/**
 * Copyright (c) 2008
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * @namespace Oryx name space for different utility methods
 * @name ORYX.Utils
*/
ORYX.Utils = {
    /**
     * General helper method for parsing a param out of current location url
     * @example
     * // Current url in Browser => "http://oryx.org?param=value"
     * ORYX.Utils.getParamFromUrl("param") // => "value" 
     * @param {Object} name
     */
	getParamFromUrl: function (name) {
		name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
		var regexS = "[\\?&]" + name + "=([^&#]*)";
		var regex = new RegExp(regexS);
		var results = regex.exec(window.location.href);
		if (results == null) {
			return null;
		}
		else {
			return results[1];
		}
	},

	adjustLightness: function () {
		return arguments[0];
	},

	adjustGradient: function (gradient, reference) {

		if (ORYX.CONFIG.DISABLE_GRADIENT && gradient) {

			var col = reference.getAttributeNS(null, "stop-color") || "#ffffff";

			$A(gradient.getElementsByTagName("stop")).each(function (stop) {
				if (stop == reference) { return; }
				stop.setAttributeNS(null, "stop-color", col);
			})
		}
	}
}
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

XMLNS = {
	ATOM: "http://www.w3.org/2005/Atom",
	XHTML: "http://www.w3.org/1999/xhtml",
	ERDF: "http://purl.org/NET/erdf/profile",
	RDFS: "http://www.w3.org/2000/01/rdf-schema#",
	RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
	RAZIEL: "http://b3mn.org/Raziel",

	SCHEMA: ""
};

var Kickstart = {
	started: false,
	callbacks: [],
	alreadyLoaded: [],
	PATH: '',

	load: function () { Kickstart.kick(); },

	kick: function () {
		if (!Kickstart.started) {
			Kickstart.started = true;
			Kickstart.callbacks.each(function (callback) {
				// call the registered callback asynchronously.
				window.setTimeout(callback, 1);
			});
		}
	},

	register: function (callback) {
		//TODO Add some mutual exclusion between kick and register calls.
		with (Kickstart) {
			if (started) window.setTimeout(callback, 1);
			else Kickstart.callbacks.push(callback)
		}
	},

	/**
	 * Loads a js, assuring that it has only been downloaded once.
	 * @param {String} url the script to load.
	 */
	require: function (url) {
		// if not already loaded, include it.
		if (Kickstart.alreadyLoaded.member(url))
			return false;
		return Kickstart.include(url);
	},

	/**
	 * Loads a js, regardless of whether it has only been already downloaded.
	 * @param {String} url the script to load.
	 */
	include: function (url) {

		// prepare a script tag and place it in html head.
		var head = document.getElementsByTagNameNS(XMLNS.XHTML, 'head')[0];
		var s = document.createElementNS(XMLNS.XHTML, "script");
		s.setAttributeNS(XMLNS.XHTML, 'type', 'text/javascript');
		s.src = Kickstart.PATH + url;

		//TODO macht es sinn, dass neue skript als letztes kind in den head
		// einzubinden (stichwort reihenfolge der skript tags)?
		head.appendChild(s);

		// remember this url.
		Kickstart.alreadyLoaded.push(url);

		return true;
	}
}

// register kickstart as the new onload event listener on current window.
// 注册kickstart.load在到window.load事件. 可以用于加载js
// previous listener(s) are triggered to launch with kickstart.
Event.observe(window, 'load', Kickstart.load);/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

var ERDF = {

	LITERAL: 0x01,
	RESOURCE: 0x02,
	DELIMITERS: ['.', '-'],
	HASH: '#',
	HYPHEN: "-",

	schemas: [],
	callback: undefined,
	log: undefined,

	init: function (callback) {

		// init logging.
		//ERDF.log = Log4js.getLogger("oryx");
		//ERDF.log.setLevel(Log4js.Level.ALL);
		//ERDF.log.addAppender(new ConsoleAppender(ERDF.log, false));

		//if(ERDF.log.isTraceEnabled())
		//	ERDF.log.trace("ERDF Parser is initialized.");

		// register callbacks and default schemas.
		ERDF.callback = callback;
		ERDF.registerSchema('schema', XMLNS.SCHEMA);
		ERDF.registerSchema('rdfs', XMLNS.RDFS);
	},

	run: function () {

		//if(ERDF.log.isTraceEnabled())
		//	ERDF.log.trace("ERDF Parser is running.");

		// do the work.
		return ERDF._checkProfile() && ERDF.parse();
	},

	parse: function () {

		//(ERDF.log.isDebugEnabled())
		//	ERDF.log.debug("Begin parsing document metadata.");

		// time measuring
		ERDF.__startTime = new Date();

		var bodies = document.getElementsByTagNameNS(XMLNS.XHTML, 'body');
		var subject = { type: ERDF.RESOURCE, value: '' };

		var result = ERDF._parseDocumentMetadata() &&
			ERDF._parseFromTag(bodies[0], subject);

		// time measuring
		ERDF.__stopTime = new Date();

		var duration = (ERDF.__stopTime - ERDF.__startTime) / 1000.;
		//alert('ERDF parsing took ' + duration + ' s.');

		return result;
	},

	_parseDocumentMetadata: function () {

		// get links from head element.
		var heads = document.getElementsByTagNameNS(XMLNS.XHTML, 'head');
		var links = heads[0].getElementsByTagNameNS(XMLNS.XHTML, 'link');
		var metas = heads[0].getElementsByTagNameNS(XMLNS.XHTML, 'meta');

		// process links first, since they could contain schema definitions.
		$A(links).each(function (link) {
			var properties = link.getAttribute('rel');
			var reversedProperties = link.getAttribute('rev');
			var value = link.getAttribute('href');

			ERDF._parseTriplesFrom(
				ERDF.RESOURCE, '',
				properties,
				ERDF.RESOURCE, value);

			ERDF._parseTriplesFrom(
				ERDF.RESOURCE, value,
				reversedProperties,
				ERDF.RESOURCE, '');
		});

		// continue with metas.
		$A(metas).each(function (meta) {
			var property = meta.getAttribute('name');
			var value = meta.getAttribute('content');

			ERDF._parseTriplesFrom(
				ERDF.RESOURCE, '',
				property,
				ERDF.LITERAL, value);
		});

		return true;
	},

	_parseFromTag: function (node, subject, depth) {

		// avoid parsing non-xhtml content.
		if (node.namespaceURI != XMLNS.XHTML) { return; }

		// housekeeping.
		if (!depth) depth = 0;
		var id = node.getAttribute('id');

		// some logging.
		//if(ERDF.log.isTraceEnabled())
		//	ERDF.log.trace(">".times(depth) + " Parsing " + node.nodeName + " ("+node.nodeType+") for data on " +
		//		((subject.type == ERDF.RESOURCE) ? ('&lt;' + subject.value + '&gt;') : '') +
		//		((subject.type == ERDF.LITERAL) ? '"' + subject.value + '"' : ''));

		/* triple finding! */

		// in a-tags...
		if (node.nodeName.endsWith(':a') || node.nodeName == 'a') {
			var properties = node.getAttribute('rel');
			var reversedProperties = node.getAttribute('rev');
			var value = node.getAttribute('href');
			var title = node.getAttribute('title');
			var content = node.textContent;

			// rel triples
			ERDF._parseTriplesFrom(
				subject.type, subject.value,
				properties,
				ERDF.RESOURCE, value,
				function (triple) {
					var label = title ? title : content;

					// label triples
					ERDF._parseTriplesFrom(
						triple.object.type, triple.object.value,
						'rdfs.label',
						ERDF.LITERAL, label);
				});

			// rev triples
			ERDF._parseTriplesFrom(
				subject.type, subject.value,
				reversedProperties,
				ERDF.RESOURCE, '');

			// type triples
			ERDF._parseTypeTriplesFrom(
				subject.type, subject.value,
				properties);

			// in img-tags...
		} else if (node.nodeName.endsWith(':img') || node.nodeName == 'img') {
			var properties = node.getAttribute('class');
			var value = node.getAttribute('src');
			var alt = node.getAttribute('alt');

			ERDF._parseTriplesFrom(
				subject.type, subject.value,
				properties,
				ERDF.RESOURCE, value,
				function (triple) {
					var label = alt;

					// label triples
					ERDF._parseTriplesFrom(
						triple.object.type, triple.object.value,
						'rdfs.label',
						ERDF.LITERAL, label);
				});

		}

		// in every tag
		var properties = node.getAttribute('class');
		var title = node.getAttribute('title');
		var content = node.textContent;
		var label = title ? title : content;

		// regular triples
		ERDF._parseTriplesFrom(
			subject.type, subject.value,
			properties,
			ERDF.LITERAL, label);

		if (id) subject = { type: ERDF.RESOURCE, value: ERDF.HASH + id };

		// type triples
		ERDF._parseTypeTriplesFrom(
			subject.type, subject.value,
			properties);

		// parse all children that are element nodes.
		var children = node.childNodes;
		if (children) $A(children).each(function (_node) {
			if (_node.nodeType == _node.ELEMENT_NODE)
				ERDF._parseFromTag(_node, subject, depth + 1);
		});
	},

	_parseTriplesFrom: function (subjectType, subject, properties,
		objectType, object, callback) {

		if (!properties) return;
		properties.toLowerCase().split(' ').each(function (property) {

			//if(ERDF.log.isTraceEnabled())
			//	ERDF.log.trace("Going for property " + property);

			var schema = ERDF.schemas.find(function (schema) {
				return false || ERDF.DELIMITERS.find(function (delimiter) {
					return property.startsWith(schema.prefix + delimiter);
				});
			});

			if (schema && object) {
				property = property.substring(
					schema.prefix.length + 1, property.length);
				var triple = ERDF.registerTriple(
					new ERDF.Resource(subject),
					{ prefix: schema.prefix, name: property },
					(objectType == ERDF.RESOURCE) ?
						new ERDF.Resource(object) :
						new ERDF.Literal(object));

				if (callback) callback(triple);
			}
		});
	},

	_parseTypeTriplesFrom: function (subjectType, subject, properties, callback) {

		if (!properties) return;
		properties.toLowerCase().split(' ').each(function (property) {

			//if(ERDF.log.isTraceEnabled())
			//	ERDF.log.trace("Going for property " + property);

			var schema = ERDF.schemas.find(function (schema) {
				return false || ERDF.DELIMITERS.find(function (delimiter) {
					return property.startsWith(ERDF.HYPHEN + schema.prefix + delimiter);
				});
			});

			if (schema && subject) {
				property = property.substring(schema.prefix.length + 2, property.length);
				var triple = ERDF.registerTriple(
					(subjectType == ERDF.RESOURCE) ?
						new ERDF.Resource(subject) :
						new ERDF.Literal(subject),
					{ prefix: 'rdf', name: 'type' },
					new ERDF.Resource(schema.namespace + property));
				if (callback) callback(triple);
			}
		});
	},

	/**
	 * Checks for ERDF profile declaration in head of document.
	 */
	_checkProfile: function () {

		// get profiles from head element.
		var heads = document.getElementsByTagNameNS(XMLNS.XHTML, 'head');
		var profiles = heads[0].getAttribute("profile");
		var found = false;

		// if erdf profile is contained.
		if (profiles && profiles.split(" ").member(XMLNS.ERDF)) {

			// pass check.
			//if(ERDF.log.isTraceEnabled())
			//	ERDF.log.trace("Found ERDF profile " + XMLNS.ERDF);
			return true;

		} else {

			// otherwise fail check.
			//if(ERDF.log.isFatalEnabled())
			//	ERDF.log.fatal("No ERDF profile found.");
			return false;
		}
	},

	__stripHashes: function (s) {
		return (s && s.substring(0, 1) == '#') ? s.substring(1, s.length) : s;
	},

	registerSchema: function (prefix, namespace) {

		// TODO check whether already registered, if so, complain.
		ERDF.schemas.push({
			prefix: prefix,
			namespace: namespace
		});

		//if(ERDF.log.isDebugEnabled())
		//	ERDF.log.debug("Prefix '"+prefix+"' for '"+namespace+"' registered.");
	},

	registerTriple: function (subject, predicate, object) {

		// if prefix is schema, this is a schema definition.
		if (predicate.prefix.toLowerCase() == 'schema')
			this.registerSchema(predicate.name, object.value);

		var triple = new ERDF.Triple(subject, predicate, object);
		ERDF.callback(triple);

		//if(ERDF.log.isInfoEnabled())
		//	ERDF.log.info(triple)

		// return the registered triple.
		return triple;
	},

	__enhanceObject: function () {

		/* Resource state querying methods */
		this.isResource = function () {
			return this.type == ERDF.RESOURCE
		};
		this.isLocal = function () {
			return this.isResource() && this.value.startsWith('#')
		};
		this.isCurrentDocument = function () {
			return this.isResource() && (this.value == '')
		};

		/* Resource getter methods.*/
		this.getId = function () {
			return this.isLocal() ? ERDF.__stripHashes(this.value) : false;
		};

		/* Liiteral state querying methods  */
		this.isLiteral = function () {
			return this.type == ERDF.LIITERAL
		};
	},

	serialize: function (literal) {

		if (!literal) {
			return "";
		} else if (literal.constructor == String) {
			return literal;
		} else if (literal.constructor == Boolean) {
			return literal ? 'true' : 'false';
		} else {
			return literal.toString();
		}
	}
};


ERDF.Triple = function (subject, predicate, object) {

	this.subject = subject;
	this.predicate = predicate;
	this.object = object;

	this.toString = function () {

		return "[ERDF.Triple] " +
			this.subject.toString() + ' ' +
			this.predicate.prefix + ':' + this.predicate.name + ' ' +
			this.object.toString();
	};
};

ERDF.Resource = function (uri) {

	this.type = ERDF.RESOURCE;
	this.value = uri;
	ERDF.__enhanceObject.apply(this);

	this.toString = function () {
		return '&lt;' + this.value + '&gt;';
	}

};

ERDF.Literal = function (literal) {

	this.type = ERDF.LITERAL;
	this.value = ERDF.serialize(literal);
	ERDF.__enhanceObject.apply(this);

	this.toString = function () {
		return '"' + this.value + '"';
	}
};/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/*
 * Save and triple generation behaviour. Use this area to configure
 * data management to your needs.
 */
var USE_ASYNCHRONOUS_REQUESTS = true;
var DISCARD_UNUSED_TRIPLES = true;
var PREFER_SPANS_OVER_DIVS = true;
var PREFER_TITLE_OVER_TEXTNODE = false;
var RESOURCE_ID_PREFIX = 'resource';

var SHOW_DEBUG_ALERTS_WHEN_SAVING = false;
var SHOW_EXTENDED_DEBUG_INFORMATION = false;

/*
 * Back end specific workarounds.
 */

var USE_ARESS_WORKAROUNDS = true;

/*
 * Data management constants. Do not change these, as they are used
 * both internally and externally to communicate on events and to identify
 * command object actions in triple production and embedding rules.
 */

// Resource constants
var RESOURCE_CREATED = 0x01;
var RESOURCE_REMOVED = 0x02;
var RESOURCE_SAVED = 0x04;
var RESOURCE_RELOADED = 0x08;
var RESOURCE_SYNCHRONIZED = 0x10;

// Triple constants
var TRIPLE_REMOVE = 0x01;
var TRIPLE_ADD = 0x02;
var TRIPLE_RELOAD = 0x04;
var TRIPLE_SAVE = 0x08;

var PROCESSDATA_REF = 'processdata';

// HTTP status code constants
//
//// 2xx
//const 200_OK =			'Ok';
//const 201_CREATED =		'Created';
//const 202_ACCEPTED =		'Accepted';
//const 204_NO_CONTENT =	'No Content';
//
//// 3xx
//const 301_MOVED_PERMANENTLY =	'Moved Permanently';
//const 302_MOVED_TEMPORARILY =	'Moved Temporarily';
//const 304_NOT_MODIFIED =		'Not Modified';
//
//// 4xx
//const 400_BAD_REQUEST =	'Bad Request';
//const 401_UNAUTHORIZED =	'Unauthorized';
//const 403_FORBIDDEN =		'Forbidden';
//const 404_NOT_FOUND =		'Not Found';
//const 409_CONFLICT =		'Conflict';
//
//// 5xx
//const 500_INTERNAL_SERVER_ERROR =		'Internal Server Error';
//const 501_NOT_IMPLEMENTED =			'Not Implemented';
//const 502_BAD_GATEWAY =				'Bad Gateway';
//const 503_SERVICE_UNAVAILABLE =		'Service Unavailable';
//
/**
 * The Data Management object. Use this one when interacting with page internal
 * data. Initialize data management by DataManager.init();
 * @class DataManager
 */
var DataManager = {

	/**
	 * The init method should be called once in the DataManagers lifetime.
	 * It causes the DataManager to initialize itself, the erdf parser, do all
	 * neccessary registrations and configurations, to run the parser and
	 * from then on deliver all resulting triples.
	 * No parameters needed are needed in a call to this method.
	 */
	init: function () {
		ERDF.init(DataManager._registerTriple);
		DataManager.__synclocal();
	},

	/**
	 * This triple array is meant to be the whole knowledge of the DataManager.
	 */
	_triples: [],

	/**
	 * This method is meant for callback from erdf parsing. It is not to be
	 * used in another way than to add triples to the triple store.
	 * @param {Object} triple the triple to add to the triple store.
	 */
	_registerTriple: function (triple) {
		DataManager._triples.push(triple)
	},

	/**
	 * The __synclocal method is for internal usage only.
	 * It performs synchronization with the local document, that is, the triple
	 * store is adjustet to the content of the document, which could have been
	 * changed by any other applications running on the same page.
	 */
	__synclocal: function () {
		DataManager._triples = [];
		ERDF.run();
	},

	/**
	 * Makes the shape passed into this method synchronize itself with the DOM.
	 * This method returns the shapes resource object for further manipulation.
	 * @param {Object} shape
	 */
	__synchronizeShape: function (shape) {

		var r = ResourceManager.getResource(shape.resourceId);
		var serialize = shape.serialize();

		// store all serialize values
		serialize.each(function (ser) {

			var resource = (ser.type == 'resource');
			var _triple = new ERDF.Triple(
				new ERDF.Resource(shape.resourceId),
				{ prefix: ser.prefix, name: ser.name },
				resource ?
					new ERDF.Resource(ser.value) :
					new ERDF.Literal(ser.value)
			);
			DataManager.setObject(_triple);
		});

		return r;
	},

	__storeShape: function (shape) {

		// first synchronize the shape,
		var resource = DataManager.__synchronizeShape(shape);

		// then save the synchronized dom.
		resource.save();
	},

	__forceExistance: function (shape) {

		if (!$(shape.resourceId)) {

			if (!$$('.' + PROCESSDATA_REF)[0])
				DataManager.graft(XMLNS.XHTML,
					document.getElementsByTagNameNS(XMLNS.XHTML, 'body').item(0), ['div', { 'class': PROCESSDATA_REF, 'style': 'display:none;' }]);

			// object is literal
			DataManager.graft(XMLNS.XHTML,
				$$('.' + PROCESSDATA_REF)[0], [

				'div', {
					'id': shape.resourceId,
					//This should be done in a more dynamic way!!!!!
					'class': (shape instanceof ORYX.Core.Canvas) ? "-oryx-canvas" : undefined
				}
			]);

		} else {
			var resource = $(shape.resourceId)
			var children = $A(resource.childNodes)
			children.each(function (child) {
				resource.removeChild(child);
			});
		};
	},

	__persistShape: function (shape) {

		// a shape serialization.
		var shapeData = shape.serialize();

		// initialize a triple array and construct a shape resource
		// to be used in triple generation.
		var triplesArray = [];
		var shapeResource = new ERDF.Resource(shape.resourceId);

		// remove all triples for this particular shape's resource
		DataManager.removeTriples(DataManager.query(
			shapeResource, undefined, undefined));

		// for each data set in the shape's serialization
		shapeData.each(function (data) {

			// construct a triple's value
			var value = (data.type == 'resource') ?
				new ERDF.Resource(data.value) :
				new ERDF.Literal(data.value);

			// construct triple and add it to the DOM.
			DataManager.addTriple(new ERDF.Triple(
				shapeResource,
				{ prefix: data.prefix, name: data.name },
				value
			));
		});
	},

	__persistDOM: function (facade) {

		// getChildShapes gets all shapes (nodes AND edges), deep flag
		// makes it return a flattened child hierarchy.

		var canvas = facade.getCanvas();
		var shapes = canvas.getChildShapes(true);
		var result = '';

		// persist all shapes.
		shapes.each(function (shape) {
			DataManager.__forceExistance(shape);
		});
		//DataManager.__synclocal();

		DataManager.__renderCanvas(facade);
		result += DataManager.serialize(
			$(ERDF.__stripHashes(facade.getCanvas().resourceId)), true);

		shapes.each(function (shape) {

			DataManager.__persistShape(shape);
			result += DataManager.serialize(
				$(ERDF.__stripHashes(shape.resourceId)), true);
		});

		//result += DataManager.__renderCanvas(facade);

		return result;
	},

	__renderCanvas: function (facade) {

		var canvas = facade.getCanvas();
		var stencilSets = facade.getStencilSets();
		var shapes = canvas.getChildShapes(true);

		DataManager.__forceExistance(canvas);

		DataManager.__persistShape(canvas);

		var shapeResource = new ERDF.Resource(canvas.resourceId);

		// remove all triples for this particular shape's resource
		DataManager.removeTriples(DataManager.query(
			shapeResource, undefined, undefined));

		DataManager.addTriple(new ERDF.Triple(
			shapeResource,
			{ prefix: "oryx", name: "mode" },
			new ERDF.Literal("writable")
		));

		DataManager.addTriple(new ERDF.Triple(
			shapeResource,
			{ prefix: "oryx", name: "mode" },
			new ERDF.Literal("fullscreen")
		));

		stencilSets.values().each(function (stencilset) {
			DataManager.addTriple(new ERDF.Triple(
				shapeResource,
				{ prefix: "oryx", name: "stencilset" },
				new ERDF.Resource(stencilset.source().replace(/&/g, "%26"))
			));

			DataManager.addTriple(new ERDF.Triple(
				shapeResource,
				{ prefix: "oryx", name: "ssnamespace" },
				new ERDF.Resource(stencilset.namespace())
			));

			stencilset.extensions().keys().each(function (extension) {
				DataManager.addTriple(new ERDF.Triple(
					shapeResource,
					{ prefix: "oryx", name: "ssextension" },
					new ERDF.Literal(extension)
				));
			});
		});

		shapes.each(function (shape) {
			DataManager.addTriple(new ERDF.Triple(
				shapeResource,
				{ prefix: "oryx", name: "render" },
				new ERDF.Resource("#" + shape.resourceId)
			));
		});
	},

	__counter: 0,
	__provideId: function () {

		while ($(RESOURCE_ID_PREFIX + DataManager.__counter))
			DataManager.__counter++;

		return RESOURCE_ID_PREFIX + DataManager.__counter;
	},

	serializeDOM: function (facade) {

		return DataManager.__persistDOM(facade);
	},

	syncGlobal: function (facade) {

		return DataManager.__syncglobal(facade);
	},

	/**
	 * This method is used to synchronize local DOM with remote resources.
	 * Local changes are commited to the server, and remote changes are
	 * performed to the local document.
	 * @param {Object} facade The facade of the editor that holds certain
	 * resource representations as shapes.
	 */
	__syncglobal: function (facade) {

		// getChildShapes gets all shapes (nodes AND edges), deep flag
		// makes it return a flattened child hierarchy.

		var canvas = facade.getCanvas();
		var shapes = canvas.getChildShapes(true);

		// create dummy resource representations in the dom
		// for all shapes that were newly created.

		shapes.select(function (shape) {

			// select shapes without resource id.

			return !($(shape.resourceId));

		}).each(function (shape) {

			// create new resources for them.
			if (USE_ARESS_WORKAROUNDS) {

				/*
				 * This is a workaround due to a bug in aress. Resources are
				 * ignoring changes to raziel:type property once they are
				 * created. As long as this is not fixed, the resource is now
				 * being created using a randomly guessed id, this temporary id
				 * is then used in references and the appropriate div is being
				 * populated with properties.
				 * 
				 * AFTER THIS PHASE THE DATA IS INCONSISTENT AS REFERENCES POINT
				 * TO IDS THAT ARE UNKNOWN TO THE BACK END.
				 * 
				 * After the resource is actually created in aress, it gets an id
				 * that is persistent. All shapes are then being populated with the
				 * correct id references and stored on the server.
				 * 
				 * AFTER THE SAVE PROCESS HAS RETURNED, THE DATA IS CONSISTENT
				 * REGARDING THE ID REFERENCES AGAIN.
				 */

				var razielType = shape.properties['raziel-type'];

				var div = '<div xmlns="http://www.w3.org/1999/xhtml">' +
					'<span class="raziel-type">' + razielType + '</span></div>';

				var r = ResourceManager.__createResource(div);
				shape.resourceId = r.id();

			} else {

				var r = ResourceManager.__createResource();
				shape.resourceId = r.id();
			}

		});

		shapes.each(function (shape) {

			// store all shapes.
			DataManager.__storeShape(shape);
		});
	},

	/**
	 * This method serializes a single div into a string that satisfies the
	 * client/server communication protocol. It ingnores all elements that have
	 * an attribute named class that includes 'transient'.
	 * @param {Object} node the element to serialize.
	 * @param {Object} preserveNamespace whether to preserve the parent's
	 *                 namespace. If you are not sure about namespaces, provide
	 *                 just the element to be serialized.
	 */
	serialize: function (node, preserveNamespace) {

		if (node.nodeType == node.ELEMENT_NODE) {
			// serialize an element node.

			var children = $A(node.childNodes);
			var attributes = $A(node.attributes);
			var clazz = new String(node.getAttribute('class'));
			var ignore = clazz.split(' ').member('transient');

			// ignore transients.

			if (ignore)
				return '';

			// start serialization.

			var result = '<' + node.nodeName;

			// preserve namespace?
			if (!preserveNamespace)
				result += ' xmlns="' + (node.namespaceURI ? node.namespaceURI : XMLNS.XHTML) + '" xmlns:oryx="http://oryx-editor.org"';

			// add all attributes.

			attributes.each(function (attribute) {
				result += ' ' + attribute.nodeName + '="' +
					attribute.nodeValue + '"';
			});

			// close if no children.

			if (children.length == 0)
				result += '/>';

			else {

				// serialize all children.

				result += '>';
				children.each(function (_node) {
					result += DataManager.serialize(_node, true)
				});
				result += '</' + node.nodeName + '>'
			}

			return result;

		} else if (node.nodeType == node.TEXT_NODE) {

			// serialize a text node.
			return node.nodeValue;
		}

		//TODO serialize cdata areas also.
		//TODO work on namespace awareness.
	},

	addTriple: function (triple) {

		// assert the subject is a resource

		if (!triple.subject.type == ERDF.LITERAL)
			throw 'Cannot add the triple ' + triple.toString() +
			' because the subject is not a resource.'

		// get the element which represents this triple's subject.
		var elementId = ERDF.__stripHashes(triple.subject.value);
		var element = $(elementId);

		// assert the subject is inside this document.
		if (!element)
			throw 'Cannot add the triple ' + triple.toString() +
			' because the subject "' + elementId + '" is not in the document.';

		if (triple.object.type == ERDF.LITERAL)

			// object is literal
			DataManager.graft(XMLNS.XHTML, element, [
				'span', {
					'class': (triple.predicate.prefix + "-" +
						triple.predicate.name)
				}, triple.object.value.escapeHTML()
			]);

		else {

			// object is resource
			DataManager.graft(XMLNS.XHTML, element, [
				'a', {
					'rel': (triple.predicate.prefix + "-" +
						triple.predicate.name), 'href': triple.object.value
				}
			]);

		}

		return true;
	},

	removeTriples: function (triples) {

		// alert('Removing ' +triples.length+' triples.');

		// from all the triples select those ...
		var removed = triples.select(

			function (triple) {

				// TODO remove also from triple store.
				// ... that were actually removed.
				return DataManager.__removeTriple(triple);
			});

		// sync and return removed triples.
		// DataManager.__synclocal();
		return removed;
	},

	removeTriple: function (triple) {

		// remember whether the triple was actually removed.
		var result = DataManager.__removeTriple(triple);

		// sync and return removed triples.
		// DataManager.__synclocal();
		return result;
	},

	__removeTriple: function (triple) {

		// assert the subject is a resource
		if (!triple.subject.type == ERDF.LITERAL)

			throw 'Cannot remove the triple ' + triple.toString() +
			' because the subject is not a resource.';

		// get the element which represents this triple's subject.
		var elementId = ERDF.__stripHashes(triple.subject.value);
		var element = $(elementId);

		// assert the subject is inside this document.
		if (!element)

			throw 'Cannot remove the triple ' + triple.toString() +
			' because the subject is not in the document.';

		if (triple.object.type == ERDF.LITERAL) {

			// continue searching actively for the triple.
			var result = DataManager.__removeTripleRecursively(triple, element);
			return result;
		}
	},

	__removeTripleRecursively: function (triple, continueFrom) {

		// return when this node is not an element node.
		if (continueFrom.nodeType != continueFrom.ELEMENT_NODE)
			return false;

		var classes = new String(continueFrom.getAttribute('class'));
		var children = $A(continueFrom.childNodes);

		if (classes.include(triple.predicate.prefix + '-' + triple.predicate.name)) {

			var content = continueFrom.textContent;
			if ((triple.object.type == ERDF.LITERAL) &&
				(triple.object.value == content))

				continueFrom.parentNode.removeChild(continueFrom);

			return true;

		} else {

			children.each(function (_node) {
				DataManager.__removeTripleRecursively(triple, _node)
			});
			return false;
		}

	},

	/**
	 * graft() function
	 * Originally by Sean M. Burke from interglacial.com, altered for usage with
	 * SVG and namespace (xmlns) support. Be sure you understand xmlns before
	 * using this funtion, as it creates all grafted elements in the xmlns
	 * provided by you and all element's attribures in default xmlns. If you
	 * need to graft elements in a certain xmlns and wish to assign attributes
	 * in both that and another xmlns, you will need to do stepwise grafting,
	 * adding non-default attributes yourself or you'll have to enhance this
	 * function. Latter, I would appreciate: martin�apfelfabrik.de
	 * @param {Object} namespace The namespace in which
	 * 					elements should be grafted.
	 * @param {Object} parent The element that should contain the grafted
	 * 					structure after the function returned.
	 * @param {Object} t the crafting structure.
	 * @param {Object} doc the document in which grafting is performed.
	 */
	graft: function (namespace, parent, t, doc) {

		doc = (doc || (parent && parent.ownerDocument) || document);
		var e;
		if (t === undefined) {
			echo("Can't graft an undefined value");
		} else if (t.constructor == String) {
			e = doc.createTextNode(t);
		} else {
			for (var i = 0; i < t.length; i++) {
				if (i === 0 && t[i].constructor == String) {
					var snared = t[i].match(/^([a-z][a-z0-9]*)\.([^\s\.]+)$/i);
					if (snared) {
						e = doc.createElementNS(namespace, snared[1]);
						e.setAttributeNS(null, 'class', snared[2]);
						continue;
					}
					snared = t[i].match(/^([a-z][a-z0-9]*)$/i);
					if (snared) {
						e = doc.createElementNS(namespace, snared[1]);  // but no class
						continue;
					}

					// Otherwise:
					e = doc.createElementNS(namespace, "span");
					e.setAttribute(null, "class", "namelessFromLOL");
				}

				if (t[i] === undefined) {
					echo("Can't graft an undefined value in a list!");
				} else if (t[i].constructor == String || t[i].constructor == Array) {
					this.graft(namespace, e, t[i], doc);
				} else if (t[i].constructor == Number) {
					this.graft(namespace, e, t[i].toString(), doc);
				} else if (t[i].constructor == Object) {
					// hash's properties => element's attributes
					for (var k in t[i]) { e.setAttributeNS(null, k, t[i][k]); }
				} else if (t[i].constructor == Boolean) {
					this.graft(namespace, e, t[i] ? 'true' : 'false', doc);
				} else
					throw "Object " + t[i] + " is inscrutable as an graft arglet.";
			}
		}

		if (parent) parent.appendChild(e);

		return Element.extend(e); // return the topmost created node
	},

	setObject: function (triple) {

		/**
		 * Erwartungen von Arvid an diese Funktion:
		 * - Es existiert genau ein triple mit dem Subjekt und Praedikat,
		 *   das uebergeben wurde, und dieses haelt uebergebenes Objekt.
		 */

		var triples = DataManager.query(
			triple.subject,
			triple.predicate,
			undefined
		);

		DataManager.removeTriples(triples);

		DataManager.addTriple(triple);

		return true;
	},

	query: function (subject, predicate, object) {

		/*
		 * Typical triple.
		 *	{value: subject, type: subjectType},
		 *	{prefix: schema.prefix, name: property},
		 *	{value: object, type: objectType});
		 */

		return DataManager._triples.select(function (triple) {

			var select = ((subject) ?
				(triple.subject.type == subject.type) &&
				(triple.subject.value == subject.value) : true);
			if (predicate) {
				select = select && ((predicate.prefix) ?
					(triple.predicate.prefix == predicate.prefix) : true);
				select = select && ((predicate.name) ?
					(triple.predicate.name == predicate.name) : true);
			}
			select = select && ((object) ?
				(triple.object.type == object.type) &&
				(triple.object.value == object.value) : true);
			return select;
		});
	}
}

Kickstart.register(DataManager.init);

function assert(expr, m) { if (!expr) throw m; };

function DMCommand(action, triple) {

	// store action and triple.
	this.action = action;
	this.triple = triple;

	this.toString = function () {
		return 'Command(' + action + ', ' + triple + ')';
	};
}

function DMCommandHandler(nextHandler) {

	/**
	 * Private method to set the next handler in the Chain of Responsibility
	 * (see http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern for
	 * details).
	 * @param {DMCommandHandler} handler The handler that is next in the chain.
	 */
	this.__setNext = function (handler) {
		var _next = this.__next;
		this.__next = nextHandler;
		return _next ? _next : true;
	};
	this.__setNext(nextHandler);

	/**
	 * Invokes the next handler. If there is no next handler, this method
	 * returns false, otherwise it forwards the result of the handling.
	 * @param {Object} command The command object to be processed.
	 */
	this.__invokeNext = function (command) {
		return this.__next ? this.__next.handle(command) : false;
	};

	/**
	 * Handles a command. The abstract method process() is called with the
	 * command object that has been passed. If the process method catches the
	 * command (returns true on completion), the handle() method returns true.
	 * If the process() method doesn't catch the command, the next handler will
	 * be invoked.
	 * @param {Object} command The command object to be processed.
	 */
	this.handle = function (command) {
		return this.process(command) ? true : this.__invokeNext(command);
	}

	/**
	 * Empty process() method returning false. If javascript knew abstract
	 * class members, this would be one.
	 * @param {Object} command The command object to process.
	 */
	this.process = function (command) { return false; };
};

/**
 * This Handler manages the addition and the removal of meta elements in the
 * head of the document.
 * @param {DMCommandHandler} next The handler that is next in the chain.
 */
function MetaTagHandler(next) {

	DMCommandHandler.apply(this, [next]);
	this.process = function (command) {

		with (command.triple) {

			/* assert prerequisites */
			if (!(
				(subject instanceof ERDF.Resource) &&
				(subject.isCurrentDocument()) &&
				(object instanceof ERDF.Literal)
			)) return false;
		}

	};
};

var chain = new MetaTagHandler();
var command = new DMCommand(TRIPLE_ADD, new ERDF.Triple(
	new ERDF.Resource(''),
	'rdf:tool',
	new ERDF.Literal('')
));

/*
if(chain.handle(command))
	alert('Handled!');
*/

ResourceManager = {

	__corrupt: false,
	__latelyCreatedResource: undefined,
	__listeners: $H(),
	__token: 1,

	addListener: function (listener, mask) {

		if (!(listener instanceof Function))
			throw 'Resource event listener is not a function!';
		if (!(mask))
			throw 'Invalid mask for resource event listener registration.';

		// construct controller and token.
		var controller = { listener: listener, mask: mask };
		var token = ResourceManager.__token++;

		// add new listener.
		ResourceManager.__listeners[token] = controller;

		// return the token generated.
		return token;
	},

	removeListener: function (token) {

		// remove the listener with the token and return it.
		return ResourceManager.__listners.remove(token);
	},

	__Event: function (action, resourceId) {
		this.action = action;
		this.resourceId = resourceId;
	},

	__dispatchEvent: function (event) {

		// get all listeners. for each listener, ...
		ResourceManager.__listeners.values().each(function (controller) {

			// .. if listener subscribed to this type of event ...
			if (event.action & controller.mask)
				return controller.listener(event);
		});
	},

	getResource: function (id) {

		// get all possible resources for this.
		id = ERDF.__stripHashes(id);
		var resources = DataManager.query(
			new ERDF.Resource('#' + id),
			{ prefix: 'raziel', name: 'entry' },
			undefined
		);

		// check for consistency.
		if ((resources.length == 1) && (resources[0].object.isResource())) {
			var entryUrl = resources[0].object.value;
			return new ResourceManager.__Resource(id, entryUrl);
		}

		// else throw an error message.
		throw ('Resource with id ' + id + ' not recognized as such. ' +
			((resources.length > 1) ?
				' There is more than one raziel:entry URL.' :
				' There is no raziel:entry URL.'));

		return false;
	},

	__createResource: function (alternativeDiv) {

		var collectionUrls = DataManager.query(
			new ERDF.Resource(''),
			// TODO This will become raziel:collection in near future.
			{ prefix: 'raziel', name: 'collection' },
			undefined
		);

		// check for consistency.

		if ((collectionUrls.length == 1) &&
			(collectionUrls[0].object.isResource())) {

			// get the collection url.

			var collectionUrl = collectionUrls[0].object.value;
			var resource = undefined;

			// if there is an old id, serialize the dummy div from there,
			// otherwise create a dummy div on the fly.

			var serialization = alternativeDiv ? alternativeDiv :
				'<div xmlns="http://www.w3.org/1999/xhtml"></div>';

			ResourceManager.__request(
				'POST', collectionUrl, serialization,

				// on success
				function () {

					// get div and id that have been generated by the server.

					var response = (this.responseXML);
					var div = response.childNodes[0];
					var id = div.getAttribute('id');

					// store div in DOM
					if (!$$('.' + PROCESSDATA_REF)[0])
						DataManager.graft(XMLNS.XHTML,
							document.getElementsByTagNameNS(XMLNS.XHTML, 'body').item(0), ['div', { 'class': PROCESSDATA_REF, 'style': 'display:none;' }]);

					$$('.' + PROCESSDATA_REF)[0].appendChild(div.cloneNode(true));

					// parse local erdf data once more.

					DataManager.__synclocal();

					// get new resource object.

					resource = new ResourceManager.getResource(id);

					// set up an action informing of the creation.

					ResourceManager.__resourceActionSucceeded(
						this, RESOURCE_CREATED, undefined);
				},

				function () {
					ResourceManager.__resourceActionFailed(
						this, RESOURCE_CREATED, undefined);
				},
				false
			);

			return resource;
		}

		// else
		throw 'Could not create resource! raziel:collection URL is missing!';
		return false;

	},

	__Resource: function (id, url) {

		this.__id = id;
		this.__url = url;

		/*
		 * Process URL is no longer needed to refer to the shape element on the
		 * canvas. AReSS uses the id's to gather information on fireing
		 * behaviour now.
		 */

		//		// find the process url.		
		//		var processUrl = undefined;
		//		
		//		var urls = DataManager.query(
		//			new ERDF.Resource('#'+this.__id),
		//			{prefix: 'raziel', name: 'process'},
		//			undefined
		//		);
		//		
		//		if(urls.length == 0) { throw 'The resource with the id ' +id+ ' has no process url.'};
		//		
		//		urls.each( function(triple) {
		//			
		//			// if there are more urls, use the last one.
		//			processUrl = triple.object.value;
		//		});
		//		
		//		this.__processUrl = processUrl;
		//
		//		// convenience function for getting the process url.
		//		this.processUrl = function() {
		//			return this.__processUrl;
		//		}


		// convenience finction for getting the id.
		this.id = function () {
			return this.__id;
		}

		// convenience finction for getting the entry url.
		this.url = function () {
			return this.__url;
		}

		this.reload = function () {
			var _url = this.__url;
			var _id = this.__id;
			ResourceManager.__request(
				'GET', _url, null,
				function () {
					ResourceManager.__resourceActionSucceeded(
						this, RESOURCE_RELOADED, _id);
				},
				function () {
					ResourceManager.__resourceActionFailed(
						this, RESURCE_RELOADED, _id);
				},
				USE_ASYNCHRONOUS_REQUESTS
			);
		};

		this.save = function (synchronize) {
			var _url = this.__url;
			var _id = this.__id;
			data = DataManager.serialize($(_id));
			ResourceManager.__request(
				'PUT', _url, data,
				function () {
					ResourceManager.__resourceActionSucceeded(
						this, synchronize ? RESOURCE_SAVED | RESOURCE_SYNCHRONIZED : RESOURCE_SAVED, _id);
				},
				function () {
					ResourceManager.__resourceActionFailed(
						this, synchronize ? RESOURCE_SAVED | RESOURCE_SYNCHRONIZED : RESOURCE.SAVED, _id);
				},
				USE_ASYNCHRONOUS_REQUESTS
			);
		};

		this.remove = function () {
			var _url = this.__url;
			var _id = this.__id;
			ResourceManager.__request(
				'DELETE', _url, null,
				function () {
					ResourceManager.__resourceActionSucceeded(
						this, RESOURCE_REMOVED, _id);
				},
				function () {
					ResourceManager.__resourceActionFailed(
						this, RESOURCE_REMOVED, _id);
				},
				USE_ASYNCHRONOUS_REQUESTS
			);
		};
	},

	request: function (url, requestOptions) {

		var options = {
			method: 'get',
			asynchronous: true,
			parameters: {}
		};

		Object.extend(options, requestOptions || {});

		var params = Hash.toQueryString(options.parameters);
		if (params)
			url += (url.include('?') ? '&' : '?') + params;

		return ResourceManager.__request(
			options.method,
			url,
			options.data,
			(options.onSuccess instanceof Function ? function () { options.onSuccess(this); } : undefined),
			(options.onFailure instanceof Function ? function () { options.onFailure(this); } : undefined),
			options.asynchronous && USE_ASYNCHRONOUS_REQUESTS,
			options.headers);
	},

	__request: function (method, url, data, success, error, async, headers) {

		// get a request object
		var httpRequest = Try.these(

			/* do the Mozilla/Safari/Opera stuff */
			function () { return new XMLHttpRequest(); },

			/* do the IE stuff */
			function () { return new ActiveXObject("Msxml2.XMLHTTP"); },
			function () { return new ActiveXObject("Microsoft.XMLHTTP") }
		);

		// if there is no request object ...
		if (!httpRequest) {
			if (!this.__corrupt)
				throw 'This browser does not provide any AJAX functionality. You will not be able to use the software provided with the page you are viewing. Please consider installing appropriate extensions.';
			this.__corrupt = true;
			return false;
		}

		if (success instanceof Function)
			httpRequest.onload = success;
		if (error instanceof Function) {
			httpRequest.onerror = error;
		}

		var h = $H(headers)
		h.keys().each(function (key) {

			httpRequest.setRequestHeader(key, h[key]);
		});

		try {

			if (SHOW_DEBUG_ALERTS_WHEN_SAVING)

				alert(method + ' ' + url + '\n' +
					SHOW_EXTENDED_DEBUG_INFORMATION ? data : '');

			// TODO Remove synchronous calls to the server as soon as xenodot
			// handles asynchronous requests without failure.
			httpRequest.open(method, url, !async ? false : true);
			httpRequest.send(data);

		} catch (e) {
			return false;
		}
		return true;
	},

	__resourceActionSucceeded: function (transport, action, id) {

		var status = transport.status;
		var response = transport.responseText;
		var div;
		var id;
		var localDiv;
		if (SHOW_DEBUG_ALERTS_WHEN_SAVING)

			alert(status + ' ' + url + '\n' +
				SHOW_EXTENDED_DEBUG_INFORMATION ? data : '');

		// if the status code is not in 2xx, throw an error.
		if (status >= 300)
			throw 'The server responded with an error: ' + status + '\n' + (SHOW_EXTENDED_DEBUG_INFORMATION ? + data : 'If you need additional information here, including the data sent by the server, consider setting SHOW_EXTENDED_DEBUG_INFORMATION to true.');

		switch (action) {

			case RESOURCE_REMOVED:

				// get div and id
				response = (transport.responseXML);
				div = response.childNodes[0];
				id = div.getAttribute('id');

				// remove the resource from DOM
				localDiv = document.getElementById(id);
				localDiv.parentNode.removeChild(localDiv);
				break;

			case RESOURCE_CREATED:

				// nothing remains to be done.
				break;

			case RESOURCE_SAVED | RESOURCE_SYNCHRONIZED:

				DataManager.__synclocal();

			case RESOURCE_SAVED:

				// nothing remains to be done.
				break;

			case RESOURCE_RELOADED:

				// get div and id
				response = (transport.responseXML);
				div = response.childNodes[0];
				id = div.getAttribute('id');

				// remove the local resource representation from DOM
				localDiv = document.getElementById(id)
				localDiv.parentNode.removeChild(localDiv);

				// store div in DOM
				if (!$$(PROCESSDATA_REF)[0])
					DataManager.graft(XMLNS.XHTML,
						document.getElementsByTagNameNS(XMLNS.XHTML, 'body').item(0), ['div', { 'class': PROCESSDATA_REF, 'style': 'display:none;' }]);

				$$(PROCESSDATA_REF)[0].appendChild(div.cloneNode(true));
				DataManager.__synclocal();
				break;

			default:
				DataManager.__synclocal();

		}

		// dispatch to all listeners ...
		ResourceManager.__dispatchEvent(

			// ... an event describing the change that happened here.
			new ResourceManager.__Event(action, id)
		);
	},

	__resourceActionFailed: function (transport, action, id) {
		throw "Fatal: Resource action failed. There is something horribly " +
		"wrong with either the server, the transport protocol or your " +
		"online status. Sure you're online?";
	}
}/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * The super class for all classes in ORYX. Adds some OOP feeling to javascript.
 * See article "Object Oriented Super Class Method Calling with JavaScript" on
 * http://truecode.blogspot.com/2006/08/object-oriented-super-class-method.html
 * for a documentation on this. Fairly good article that points out errors in
 * Douglas Crockford's inheritance and super method calling approach.
 * Worth reading.
 * @class Clazz
 * 超级基类
 */
var Clazz = function () { };

/**
 * Empty constructor.
 * 定义基类构造函数
 * @methodOf Clazz.prototype
 */
Clazz.prototype.construct = function () { };

/**
 * Can be used to build up inheritances of classes.
 * 定义基类的继承函数
 * @example
 * var MyClass = Clazz.extend({
 *   construct: function(myParam){
 *     // Do sth.
 *   }
 * });
 * var MySubClass = MyClass.extend({
 *   construct: function(myParam){
 *     // Use this to call constructor of super class
 *     arguments.callee.$.construct.apply(this, arguments);
 *     // Do sth.
 *   }
 * });
 * @param {Object} def The definition of the new class.
 */
Clazz.extend = function (def) {
	var classDef = function () {
		if (arguments[0] !== Clazz) { this.construct.apply(this, arguments); }
	};

	var proto = new this(Clazz);
	var superClass = this.prototype;

	for (var n in def) {
		var item = def[n];
		if (item instanceof Function) item.$ = superClass;
		proto[n] = item;
	}

	classDef.prototype = proto;

	//Give this new class the same static extend method    
	classDef.extend = this.extend;
	return classDef;
};
/**
 * Copyright (c) 2010
 * Signavio GmbH
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
if (!ORYX) var ORYX = {};

if (!ORYX.CONFIG) ORYX.CONFIG = {};

/**
 * This file contains URI constants that may be used for XMLHTTPRequests.
 */

ORYX.CONFIG.ROOT_PATH = "../static/workflow/editor"; //TODO: Remove last slash!!
ORYX.CONFIG.EXPLORER_PATH = "../static/workflow/explorer";
ORYX.CONFIG.LIBS_PATH = "../static/workflow/libs";

/**
 * Regular Config
 */
ORYX.CONFIG.SERVLET_HANDLER_ROOT = "../workflowservice";
ORYX.CONFIG.SERVER_HANDLER_ROOT = "../static/workflow";
ORYX.CONFIG.SERVER_EDITOR_HANDLER = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/editor";
ORYX.CONFIG.SERVER_MODEL_HANDLER = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/model";
ORYX.CONFIG.STENCILSET_HANDLER = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/editor_stencilset?embedsvg=true&url=true&namespace=";
ORYX.CONFIG.STENCIL_SETS_URL = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/editor_stencilset";

ORYX.CONFIG.PLUGINS_CONFIG = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/editor/plugins.xml";
ORYX.CONFIG.SYNTAXCHECKER_URL = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/syntaxchecker";
ORYX.CONFIG.DEPLOY_URL = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/model/deploy";
ORYX.CONFIG.MODEL_LIST_URL = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/models";

ORYX.CONFIG.SS_EXTENSIONS_FOLDER = ORYX.CONFIG.ROOT_PATH + "/stencilsets/extensions/";
ORYX.CONFIG.SS_EXTENSIONS_CONFIG = ORYX.CONFIG.SERVER_HANDLER_ROOT + "/editor_ssextensions";
ORYX.CONFIG.ORYX_NEW_URL = "/new";
ORYX.CONFIG.BPMN_LAYOUTER = ORYX.CONFIG.ROOT_PATH + "/bpmnlayouter";/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
if (!ORYX) var ORYX = {};

if (!ORYX.CONFIG) ORYX.CONFIG = {};

/**
 * Signavio specific variablese
 */
ORYX.CONFIG.BACKEND_SWITCH = true;
ORYX.CONFIG.PANEL_LEFT_WIDTH = 250;
ORYX.CONFIG.PANEL_RIGHT_COLLAPSED = false;
ORYX.CONFIG.PANEL_RIGHT_WIDTH = 400;
ORYX.CONFIG.APPNAME = '工作流设计器';
ORYX.CONFIG.WEB_URL = "../";

ORYX.CONFIG.BLANK_IMAGE = ORYX.CONFIG.LIBS_PATH + '/ext-2.0.2/resources/images/default/s.gif';


/* Show grid line while dragging */
ORYX.CONFIG.SHOW_GRIDLINE = true;

/* Editor-Mode */
ORYX.CONFIG.MODE_READONLY = "readonly";
ORYX.CONFIG.MODE_FULLSCREEN = "fullscreen";
ORYX.CONFIG.WINDOW_HEIGHT = 400;
ORYX.CONFIG.PREVENT_LOADINGMASK_AT_READY = false;

/* Plugins */
ORYX.CONFIG.PLUGINS_ENABLED = true;
ORYX.CONFIG.PLUGINS_FOLDER = "Plugins/";

ORYX.CONFIG.BPMN20_SCHEMA_VALIDATION_ON = true;

/* Namespaces */
ORYX.CONFIG.NAMESPACE_ORYX = "http://www.b3mn.org/oryx";
ORYX.CONFIG.NAMESPACE_SVG = "http://www.w3.org/2000/svg";

/* UI */
ORYX.CONFIG.CANVAS_WIDTH = 1485;
ORYX.CONFIG.CANVAS_HEIGHT = 1050;
ORYX.CONFIG.CANVAS_RESIZE_INTERVAL = 300;
ORYX.CONFIG.SELECTED_AREA_PADDING = 4;
ORYX.CONFIG.CANVAS_BACKGROUND_COLOR = "none";
ORYX.CONFIG.GRID_DISTANCE = 30;
ORYX.CONFIG.GRID_ENABLED = true;
ORYX.CONFIG.ZOOM_OFFSET = 0.1;
ORYX.CONFIG.DEFAULT_SHAPE_MARGIN = 60;
ORYX.CONFIG.SCALERS_SIZE = 7;
ORYX.CONFIG.MINIMUM_SIZE = 20;
ORYX.CONFIG.MAXIMUM_SIZE = 10000;
ORYX.CONFIG.OFFSET_MAGNET = 15;
ORYX.CONFIG.OFFSET_EDGE_LABEL_TOP = 8;
ORYX.CONFIG.OFFSET_EDGE_LABEL_BOTTOM = 8;
ORYX.CONFIG.OFFSET_EDGE_BOUNDS = 5;
ORYX.CONFIG.COPY_MOVE_OFFSET = 30;

ORYX.CONFIG.BORDER_OFFSET = 14;

ORYX.CONFIG.MAX_NUM_SHAPES_NO_GROUP = 12;

ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER = 30;
ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET = 45;

/* Shape-Menu Align */
ORYX.CONFIG.SHAPEMENU_RIGHT = "Oryx_Right";
ORYX.CONFIG.SHAPEMENU_BOTTOM = "Oryx_Bottom";
ORYX.CONFIG.SHAPEMENU_LEFT = "Oryx_Left";
ORYX.CONFIG.SHAPEMENU_TOP = "Oryx_Top";


/* Morph-Menu Item */
ORYX.CONFIG.MORPHITEM_DISABLED = "Oryx_MorphItem_disabled";

/* Property type names */
ORYX.CONFIG.TYPE_STRING = "string";
ORYX.CONFIG.TYPE_BOOLEAN = "boolean";
ORYX.CONFIG.TYPE_INTEGER = "integer";
ORYX.CONFIG.TYPE_FLOAT = "float";
ORYX.CONFIG.TYPE_COLOR = "color";
ORYX.CONFIG.TYPE_DATE = "date";
ORYX.CONFIG.TYPE_CHOICE = "choice";
ORYX.CONFIG.TYPE_URL = "url";
ORYX.CONFIG.TYPE_DIAGRAM_LINK = "diagramlink";
ORYX.CONFIG.TYPE_COMPLEX = "complex";
ORYX.CONFIG.TYPE_MULTIPLECOMPLEX = "multiplecomplex";
ORYX.CONFIG.TYPE_TEXT = "text";
ORYX.CONFIG.TYPE_MODEL_LINK = "modellink";
ORYX.CONFIG.TYPE_LISTENER = "listener";
ORYX.CONFIG.TYPE_EPC_FREQ = "epcfrequency";
ORYX.CONFIG.TYPE_GLOSSARY_LINK = "glossarylink";
ORYX.CONFIG.TYPE_SELECTVALUEDLG = "selectvaluedlg";
ORYX.CONFIG.TYPE_SELECTPSERSONDLG = "selpersondlg"; //选择人员属性编辑 modify by gk
ORYX.CONFIG.TYPE_SELECTROLEDLG = "selectroledlg"; //选择角色属性编辑 modify by gk
ORYX.CONFIG.TYPE_SCRIPTDLG = "scriptdlg"; //脚本属性编辑 modify by gk



/* Vertical line distance of multiline labels */
ORYX.CONFIG.LABEL_LINE_DISTANCE = 2;
ORYX.CONFIG.LABEL_DEFAULT_LINE_HEIGHT = 12;

/* Open Morph Menu with Hover */
ORYX.CONFIG.ENABLE_MORPHMENU_BY_HOVER = false;


/* Editor constants come here */
ORYX.CONFIG.EDITOR_ALIGN_BOTTOM = 0x01;
ORYX.CONFIG.EDITOR_ALIGN_MIDDLE = 0x02;
ORYX.CONFIG.EDITOR_ALIGN_TOP = 0x04;
ORYX.CONFIG.EDITOR_ALIGN_LEFT = 0x08;
ORYX.CONFIG.EDITOR_ALIGN_CENTER = 0x10;
ORYX.CONFIG.EDITOR_ALIGN_RIGHT = 0x20;
ORYX.CONFIG.EDITOR_ALIGN_SIZE = 0x30;

/* Event types */
ORYX.CONFIG.EVENT_MOUSEDOWN = "mousedown";
ORYX.CONFIG.EVENT_MOUSEUP = "mouseup";
ORYX.CONFIG.EVENT_MOUSEOVER = "mouseover";
ORYX.CONFIG.EVENT_MOUSEOUT = "mouseout";
ORYX.CONFIG.EVENT_MOUSEMOVE = "mousemove";
ORYX.CONFIG.EVENT_DBLCLICK = "dblclick";
ORYX.CONFIG.EVENT_KEYDOWN = "keydown";
ORYX.CONFIG.EVENT_KEYUP = "keyup";

ORYX.CONFIG.EVENT_LOADED = "editorloaded";

ORYX.CONFIG.EVENT_EXECUTE_COMMANDS = "executeCommands";
ORYX.CONFIG.EVENT_STENCIL_SET_LOADED = "stencilSetLoaded";
ORYX.CONFIG.EVENT_SELECTION_CHANGED = "selectionchanged";
ORYX.CONFIG.EVENT_SHAPEADDED = "shapeadded";
ORYX.CONFIG.EVENT_SHAPEREMOVED = "shaperemoved";
ORYX.CONFIG.EVENT_PROPERTY_CHANGED = "propertyChanged";
ORYX.CONFIG.EVENT_DRAGDROP_START = "dragdrop.start";
ORYX.CONFIG.EVENT_SHAPE_MENU_CLOSE = "shape.menu.close";
ORYX.CONFIG.EVENT_DRAGDROP_END = "dragdrop.end";
ORYX.CONFIG.EVENT_RESIZE_START = "resize.start";
ORYX.CONFIG.EVENT_RESIZE_END = "resize.end";
ORYX.CONFIG.EVENT_DRAGDOCKER_DOCKED = "dragDocker.docked";
ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW = "highlight.showHighlight";
ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE = "highlight.hideHighlight";
ORYX.CONFIG.EVENT_LOADING_ENABLE = "loading.enable";
ORYX.CONFIG.EVENT_LOADING_DISABLE = "loading.disable";
ORYX.CONFIG.EVENT_LOADING_STATUS = "loading.status";
ORYX.CONFIG.EVENT_OVERLAY_SHOW = "overlay.show";
ORYX.CONFIG.EVENT_OVERLAY_HIDE = "overlay.hide";
ORYX.CONFIG.EVENT_ARRANGEMENT_TOP = "arrangement.setToTop";
ORYX.CONFIG.EVENT_ARRANGEMENT_BACK = "arrangement.setToBack";
ORYX.CONFIG.EVENT_ARRANGEMENT_FORWARD = "arrangement.setForward";
ORYX.CONFIG.EVENT_ARRANGEMENT_BACKWARD = "arrangement.setBackward";
ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED = "propertyWindow.propertyChanged";
ORYX.CONFIG.EVENT_LAYOUT_ROWS = "layout.rows";
ORYX.CONFIG.EVENT_LAYOUT_BPEL = "layout.BPEL";
ORYX.CONFIG.EVENT_LAYOUT_BPEL_VERTICAL = "layout.BPEL.vertical";
ORYX.CONFIG.EVENT_LAYOUT_BPEL_HORIZONTAL = "layout.BPEL.horizontal";
ORYX.CONFIG.EVENT_LAYOUT_BPEL_SINGLECHILD = "layout.BPEL.singlechild";
ORYX.CONFIG.EVENT_LAYOUT_BPEL_AUTORESIZE = "layout.BPEL.autoresize";
ORYX.CONFIG.EVENT_AUTOLAYOUT_LAYOUT = "autolayout.layout";
ORYX.CONFIG.EVENT_UNDO_EXECUTE = "undo.execute";
ORYX.CONFIG.EVENT_UNDO_ROLLBACK = "undo.rollback";
ORYX.CONFIG.EVENT_BUTTON_UPDATE = "toolbar.button.update";
ORYX.CONFIG.EVENT_LAYOUT = "layout.dolayout";
ORYX.CONFIG.EVENT_GLOSSARY_LINK_EDIT = "glossary.link.edit";
ORYX.CONFIG.EVENT_GLOSSARY_SHOW = "glossary.show.info";
ORYX.CONFIG.EVENT_GLOSSARY_NEW = "glossary.show.new";
ORYX.CONFIG.EVENT_DOCKERDRAG = "dragTheDocker";

ORYX.CONFIG.EVENT_SHOW_PROPERTYWINDOW = "propertywindow.show";
ORYX.CONFIG.EVENT_ABOUT_TO_SAVE = "file.aboutToSave";

/* Selection Shapes Highlights */
ORYX.CONFIG.SELECTION_HIGHLIGHT_SIZE = 5;
ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR = "#4444FF";
ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR2 = "#9999FF";

ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_CORNER = "corner";
ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_RECTANGLE = "rectangle";

ORYX.CONFIG.SELECTION_VALID_COLOR = "#00FF00";
ORYX.CONFIG.SELECTION_INVALID_COLOR = "#FF0000";


ORYX.CONFIG.DOCKER_DOCKED_COLOR = "#00FF00";
ORYX.CONFIG.DOCKER_UNDOCKED_COLOR = "#FF0000";
ORYX.CONFIG.DOCKER_SNAP_OFFSET = 10;

/* Copy & Paste */
ORYX.CONFIG.EDIT_OFFSET_PASTE = 10;

/* Key-Codes */
ORYX.CONFIG.KEY_CODE_X = 88;
ORYX.CONFIG.KEY_CODE_C = 67;
ORYX.CONFIG.KEY_CODE_V = 86;
ORYX.CONFIG.KEY_CODE_DELETE = 46;
ORYX.CONFIG.KEY_CODE_META = 224;
ORYX.CONFIG.KEY_CODE_BACKSPACE = 8;
ORYX.CONFIG.KEY_CODE_LEFT = 37;
ORYX.CONFIG.KEY_CODE_RIGHT = 39;
ORYX.CONFIG.KEY_CODE_UP = 38;
ORYX.CONFIG.KEY_CODE_DOWN = 40;

// TODO Determine where the lowercase constants are still used and remove them from here.
ORYX.CONFIG.KEY_Code_enter = 12;
ORYX.CONFIG.KEY_Code_left = 37;
ORYX.CONFIG.KEY_Code_right = 39;
ORYX.CONFIG.KEY_Code_top = 38;
ORYX.CONFIG.KEY_Code_bottom = 40;

/* Supported Meta Keys */

ORYX.CONFIG.META_KEY_META_CTRL = "metactrl";
ORYX.CONFIG.META_KEY_ALT = "alt";
ORYX.CONFIG.META_KEY_SHIFT = "shift";

/* Key Actions */

ORYX.CONFIG.KEY_ACTION_DOWN = "down";
ORYX.CONFIG.KEY_ACTION_UP = "up";

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

function printf() {

	var result = arguments[0];
	for (var i = 1; i < arguments.length; i++)
		result = result.replace('%' + (i - 1), arguments[i]);
	return result;
}

// oryx constants.
var ORYX_LOGLEVEL_TRACE = 5;
var ORYX_LOGLEVEL_DEBUG = 4;
var ORYX_LOGLEVEL_INFO = 3;
var ORYX_LOGLEVEL_WARN = 2;
var ORYX_LOGLEVEL_ERROR = 1;
var ORYX_LOGLEVEL_FATAL = 0;
var ORYX_LOGLEVEL = 4;
var ORYX_CONFIGURATION_DELAY = 100;
var ORYX_CONFIGURATION_WAIT_ATTEMPTS = 10;

if (!ORYX) var ORYX = {};

ORYX = Object.extend(ORYX, {

	//set the path in the config.js file!!!!
	PATH: ORYX.CONFIG.ROOT_PATH,
	//CONFIGURATION: "config.js",

	URLS: [

		/*
		 * No longer needed, since compiled into one source file that
		 * contains all of this files concatenated in the exact order
		 * as defined in build.xml.
		 */

/*
		"scripts/Core/SVG/editpathhandler.js",
		"scripts/Core/SVG/minmaxpathhandler.js",
		"scripts/Core/SVG/pointspathhandler.js",
		"scripts/Core/SVG/svgmarker.js",
		"scripts/Core/SVG/svgshape.js",
		"scripts/Core/SVG/label.js",
		"scripts/Core/Math/math.js",		
		"scripts/Core/StencilSet/stencil.js",
		"scripts/Core/StencilSet/property.js",
		"scripts/Core/StencilSet/propertyitem.js",
		"scripts/Core/StencilSet/rules.js",
		"scripts/Core/StencilSet/stencilset.js",
		"scripts/Core/StencilSet/stencilsets.js",
		"scripts/Core/bounds.js",
		"scripts/Core/uiobject.js",
		"scripts/Core/abstractshape.js",
		"scripts/Core/canvas.js",
		"scripts/Core/main.js",
		"scripts/Core/svgDrag.js",
		"scripts/Core/shape.js",
		"scripts/Core/Controls/control.js",
		"scripts/Core/Controls/docker.js",
		"scripts/Core/Controls/magnet.js",		
		"scripts/Core/node.js",
		"scripts/Core/edge.js"
*/	],

	alreadyLoaded: [],

	configrationRetries: 0,

	Version: '0.1.1',

	availablePlugins: [],

	/**
	 * The ORYX.Log logger.
	 */
	Log: {

		__appenders: [
			{
				append: function (message) {
					console.log(message);
				},
				error: function (message) {
					console.error(message);
				},
				warn: function (message) {
					console.warn(message);
				}
			}
		],

		trace: function () {
			if (ORYX_LOGLEVEL >= ORYX_LOGLEVEL_TRACE)
				ORYX.Log.__log('TRACE', arguments);
		},
		debug: function () {
			if (ORYX_LOGLEVEL >= ORYX_LOGLEVEL_DEBUG)
				ORYX.Log.__log('DEBUG', arguments);
		},
		info: function () {
			if (ORYX_LOGLEVEL >= ORYX_LOGLEVEL_INFO)
				ORYX.Log.__log('INFO', arguments);
		},
		warn: function () {
			if (ORYX_LOGLEVEL >= ORYX_LOGLEVEL_WARN)
				ORYX.Log.__log('WARN', arguments);
		},
		error: function () {
			if (ORYX_LOGLEVEL >= ORYX_LOGLEVEL_ERROR)
				ORYX.Log.__log('ERROR', arguments);
		},
		fatal: function () {
			if (ORYX_LOGLEVEL >= ORYX_LOGLEVEL_FATAL)
				ORYX.Log.__log('FATAL', arguments);
		},

		__log: function (prefix, messageParts) {
			messageParts[0] = ("[" + prefix + "]" + messageParts[0]);
			var message = printf.apply(null, messageParts);
			ORYX.Log.__appenders.each(function (appender) {
				if (prefix === 'ERROR')
					appender.error(message);
				else if (prefix === 'WARN')
					appender.warn(message);
				else
					appender.append(message);
			});
		},

		addAppender: function (appender) {
			ORYX.Log.__appenders.push(appender);
		}
	},

	/**
	 * First bootstrapping layer. The Oryx loading procedure begins. In this
	 * step, all preliminaries that are not in the responsibility of Oryx to be
	 * met have to be checked here, such as the existance of the prototpe
	 * library in the current execution environment. After that, the second
	 * bootstrapping layer is being invoked. Failing to ensure that any
	 * preliminary condition is not met has to fail with an error.
	 */
	load: function () {

		if (ORYX.CONFIG.PREVENT_LOADINGMASK_AT_READY !== true) {
			var waitingpanel = new Ext.Window({ renderTo: Ext.getBody(), id: 'oryx-loading-panel', bodyStyle: 'padding: 8px;background:white', title: ORYX.I18N.Oryx.title, width: 'auto', height: 'auto', modal: true, resizable: false, closable: false, html: '<span style="font-size:11px;">' + ORYX.I18N.Oryx.pleaseWait + '</span>' })
			waitingpanel.show()
		}

		ORYX.Log.debug("Oryx begins loading procedure.");

		// check for prototype
		if ((typeof Prototype == 'undefined') ||
			(typeof Element == 'undefined') ||
			(typeof Element.Methods == 'undefined') ||
			parseFloat(Prototype.Version.split(".")[0] + "." +
				Prototype.Version.split(".")[1]) < 1.5)

			throw ("Application requires the Prototype JavaScript framework >= 1.5.3");

		ORYX.Log.debug("Prototype > 1.5 found.");

		// continue loading.
		ORYX._load();
	},

	/**
	 * Second bootstrapping layer. The oryx configuration is checked. When not
	 * yet loaded, config.js is being requested from the server. A repeated
	 * error in retrieving the configuration will result in an error to be
	 * thrown after a certain time of retries. Once the configuration is there,
	 * all urls that are registered with oryx loading are being requested from
	 * the server. Once everything is loaded, the third layer is being invoked.
	 */
	_load: function () {
		/*
			// if configuration not there already,
			if(!(ORYX.CONFIG)) {
				
				// if this is the first attempt...
				if(ORYX.configrationRetries == 0) {
					
					// get the path and filename.
					var configuration = ORYX.PATH + ORYX.CONFIGURATION;
		
					ORYX.Log.debug("Configuration not found, loading from '%0'.",
						configuration);
					
					// require configuration file.
					Kickstart.require(configuration);
					
				// else if attempts exceeded ...
				} else if(ORYX.configrationRetries >= ORYX_CONFIGURATION_WAIT_ATTEMPTS) {
					
					throw "Tried to get configuration" +
						ORYX_CONFIGURATION_WAIT_ATTEMPTS +
						" times from '" + configuration + "'. Giving up."
						
				} else if(ORYX.configrationRetries > 0){
					
					// point out how many attempts are left...
					ORYX.Log.debug("Waiting once more (%0 attempts left)",
						(ORYX_CONFIGURATION_WAIT_ATTEMPTS -
							ORYX.configrationRetries));
	
				}
				
				// any case: continue in a moment with increased retry count.
				ORYX.configrationRetries++;
				window.setTimeout(ORYX._load, ORYX_CONFIGURATION_DELAY);
				return;
			}
			
			ORYX.Log.info("Configuration loaded.");
			
			// load necessary scripts.
			ORYX.URLS.each(function(url) {
				ORYX.Log.debug("Requireing '%0'", url);
				Kickstart.require(ORYX.PATH + url) });
		*/
		// configurate logging and load plugins.
		ORYX.loadPlugins();
	},

	/**
	 * Third bootstrapping layer. This is where first the plugin coniguration
	 * file is loaded into oryx, analyzed, and where all plugins are being
	 * requested by the server. Afterwards, all editor instances will be
	 * initialized.
	 * 加载plugins.xml配置的插件
	 */
	loadPlugins: function () {

		// load plugins if enabled.
		if (ORYX.CONFIG.PLUGINS_ENABLED)
			ORYX._loadPlugins()
		else
			ORYX.Log.warn("Ignoring plugins, loading Core only.");

		// init the editor instances.
		init();
	},

	_loadPlugins: function () {

		// load plugin configuration file.
		var source = ORYX.CONFIG.PLUGINS_CONFIG;

		ORYX.Log.debug("正在加载配置文件 [%0]", source);

		new Ajax.Request(source, {
			asynchronous: false,
			method: 'get',
			onSuccess: function (result) {

				/*
				 * This is the method that is being called when the plugin
				 * configuration was successfully loaded from the server. The
				 * file has to be processed and the contents need to be
				 * considered for further plugin requireation.
				 */

				ORYX.Log.info("插件配置文件[%0]成功加载", source);

				// get plugins.xml content
				var resultXml = result.responseXML;

				// TODO: Describe how properties are handled.
				// Get the globale Properties
				var globalProperties = [];
				var preferences = $A(resultXml.getElementsByTagName("properties"));
				preferences.each(function (p) {

					var props = $A(p.childNodes);
					props.each(function (prop) {
						var property = new Hash();

						// get all attributes from the node and set to global properties
						var attributes = $A(prop.attributes)
						attributes.each(function (attr) { property[attr.nodeName] = attr.nodeValue });
						if (attributes.length > 0) { globalProperties.push(property) };
					});
				});


				// TODO Why are we using XML if we don't respect structure anyway?
				// for each plugin element in the configuration..
				var plugin = resultXml.getElementsByTagName("plugin");
				$A(plugin).each(function (node) {

					// get all element's attributes.
					// TODO: What about: var pluginData = $H(node.attributes) !?
					var pluginData = new Hash();
					$A(node.attributes).each(function (attr) {
						pluginData[attr.nodeName] = attr.nodeValue
					});

					// ensure there's a name attribute.
					if (!pluginData['name']) {
						ORYX.Log.error("插件的name属性为空,忽略加载此插件[%0]", pluginData);
						return;
					}

					// ensure there's a source attribute.
					if (!pluginData['source']) {
						ORYX.Log.error("插件的source属性为空,忽略加载此插件[%0]", pluginData['name']);
						return;
					}

					// Get all private Properties
					var propertyNodes = node.getElementsByTagName("property");
					var properties = [];
					$A(propertyNodes).each(function (prop) {
						var property = new Hash();

						// Get all Attributes from the Node			
						var attributes = $A(prop.attributes)
						attributes.each(function (attr) { property[attr.nodeName] = attr.nodeValue });
						if (attributes.length > 0) { properties.push(property) };

					});

					// Set all Global-Properties to the Properties
					properties = properties.concat(globalProperties);

					// Set Properties to Plugin-Data
					pluginData['properties'] = properties;

					// Get the RequieredNodes
					var requireNodes = node.getElementsByTagName("requires");
					var requires;
					$A(requireNodes).each(function (req) {
						var namespace = $A(req.attributes).find(function (attr) { return attr.name == "namespace" })
						if (namespace && namespace.nodeValue) {
							if (!requires) {
								requires = { namespaces: [] }
							}

							requires.namespaces.push(namespace.nodeValue)
						}
					});

					// Set Requires to the Plugin-Data, if there is one
					if (requires) {
						pluginData['requires'] = requires;
					}


					// Get the RequieredNodes
					var notUsesInNodes = node.getElementsByTagName("notUsesIn");
					var notUsesIn;
					$A(notUsesInNodes).each(function (not) {
						var namespace = $A(not.attributes).find(function (attr) { return attr.name == "namespace" })
						if (namespace && namespace.nodeValue) {
							if (!notUsesIn) {
								notUsesIn = { namespaces: [] }
							}

							notUsesIn.namespaces.push(namespace.nodeValue)
						}
					});

					// Set Requires to the Plugin-Data, if there is one
					if (notUsesIn) {
						pluginData['notUsesIn'] = notUsesIn;
					}


					var url = ORYX.PATH + ORYX.CONFIG.PLUGINS_FOLDER + pluginData['source'];

					ORYX.Log.debug("Requireing '%0'", url);

					// Add the Script-Tag to the Site
					//Kickstart.require(url);

					ORYX.Log.info("插件 [%0] 成功加载", pluginData['name']);

					// Add the Plugin-Data to all available Plugins
					ORYX.availablePlugins.push(pluginData);

				});

			},
			onFailure: this._loadPluginsOnFails
		});

	},

	_loadPluginsOnFails: function (result) {

		ORYX.Log.error("Plugin configuration file not available.");
	}
});

ORYX.Log.debug('注册 Oryx with Kickstart');
Kickstart.register(ORYX.load);

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.SVG) { ORYX.Core.SVG = {}; }


/**
 * EditPathHandler
 * 
 * Edit SVG paths' coordinates according to specified from-to movement and
 * horizontal and vertical scaling factors. 
 * The resulting path's d attribute is stored in instance variable d.
 * 
 * @constructor
 */
ORYX.Core.SVG.EditPathHandler = Clazz.extend({

	construct: function () {
		arguments.callee.$.construct.apply(this, arguments);

		this.x = 0;
		this.y = 0;
		this.oldX = 0;
		this.oldY = 0;
		this.deltaWidth = 1;
		this.deltaHeight = 1;

		this.d = "";
	},

	/**
	 * init
	 * 
	 * @param {float} x Target point's x-coordinate
	 * @param {float} y Target point's y-coordinate
	 * @param {float} oldX Reference point's x-coordinate
	 * @param {float} oldY Reference point's y-coordinate
	 * @param {float} deltaWidth Horizontal scaling factor
	 * @param {float} deltaHeight Vertical scaling factor
	 */
	init: function (x, y, oldX, oldY, deltaWidth, deltaHeight) {
		this.x = x;
		this.y = y;
		this.oldX = oldX;
		this.oldY = oldY;
		this.deltaWidth = deltaWidth;
		this.deltaHeight = deltaHeight;

		this.d = "";
	},

	/**
	 * editPointsAbs
	 * 
	 * @param {Array} points Array of absolutePoints
	 */
	editPointsAbs: function (points) {
		if (points instanceof Array) {
			var newPoints = [];
			var x, y;
			for (var i = 0; i < points.length; i++) {
				x = (parseFloat(points[i]) - this.oldX) * this.deltaWidth + this.x;
				i++;
				y = (parseFloat(points[i]) - this.oldY) * this.deltaHeight + this.y;
				newPoints.push(x);
				newPoints.push(y);
			}

			return newPoints;
		} else {
			//TODO error
		}
	},

	/**
	 * editPointsRel
	 * 
	 * @param {Array} points Array of absolutePoints
	 */
	editPointsRel: function (points) {
		if (points instanceof Array) {
			var newPoints = [];
			var x, y;
			for (var i = 0; i < points.length; i++) {
				x = parseFloat(points[i]) * this.deltaWidth;
				i++;
				y = parseFloat(points[i]) * this.deltaHeight;
				newPoints.push(x);
				newPoints.push(y);
			}

			return newPoints;
		} else {
			//TODO error
		}
	},

	/**
	 * arcAbs - A
	 * 
	 * @param {Number} rx
	 * @param {Number} ry
	 * @param {Number} xAxisRotation
	 * @param {Boolean} largeArcFlag
	 * @param {Boolean} sweepFlag
	 * @param {Number} x
	 * @param {Number} y
	 */
	arcAbs: function (rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) {
		var pointsAbs = this.editPointsAbs([x, y]);
		var pointsRel = this.editPointsRel([rx, ry]);

		this.d = this.d.concat(" A" + pointsRel[0] + " " + pointsRel[1] +
			" " + xAxisRotation + " " + largeArcFlag +
			" " + sweepFlag + " " + pointsAbs[0] + " " +
			pointsAbs[1] + " ");
	},

	/**
	 * arcRel - a
	 * 
	 * @param {Number} rx
	 * @param {Number} ry
	 * @param {Number} xAxisRotation
	 * @param {Boolean} largeArcFlag
	 * @param {Boolean} sweepFlag
	 * @param {Number} x
	 * @param {Number} y
	 */
	arcRel: function (rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) {
		var pointsRel = this.editPointsRel([rx, ry, x, y]);

		this.d = this.d.concat(" a" + pointsRel[0] + " " + pointsRel[1] +
			" " + xAxisRotation + " " + largeArcFlag +
			" " + sweepFlag + " " + pointsRel[2] + " " +
			pointsRel[3] + " ");
	},

	/**
	 * curvetoCubicAbs - C
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicAbs: function (x1, y1, x2, y2, x, y) {
		var pointsAbs = this.editPointsAbs([x1, y1, x2, y2, x, y]);

		this.d = this.d.concat(" C" + pointsAbs[0] + " " + pointsAbs[1] +
			" " + pointsAbs[2] + " " + pointsAbs[3] +
			" " + pointsAbs[4] + " " + pointsAbs[5] + " ");
	},

	/**
	 * curvetoCubicRel - c
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicRel: function (x1, y1, x2, y2, x, y) {
		var pointsRel = this.editPointsRel([x1, y1, x2, y2, x, y]);

		this.d = this.d.concat(" c" + pointsRel[0] + " " + pointsRel[1] +
			" " + pointsRel[2] + " " + pointsRel[3] +
			" " + pointsRel[4] + " " + pointsRel[5] + " ");
	},

	/**
	 * linetoHorizontalAbs - H
	 * 
	 * @param {Number} x
	 */
	linetoHorizontalAbs: function (x) {
		var pointsAbs = this.editPointsAbs([x, 0]);

		this.d = this.d.concat(" H" + pointsAbs[0] + " ");
	},

	/**
	 * linetoHorizontalRel - h
	 * 
	 * @param {Number} x
	 */
	linetoHorizontalRel: function (x) {
		var pointsRel = this.editPointsRel([x, 0]);

		this.d = this.d.concat(" h" + pointsRel[0] + " ");
	},

	/**
	 * linetoAbs - L
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	linetoAbs: function (x, y) {
		var pointsAbs = this.editPointsAbs([x, y]);

		this.d = this.d.concat(" L" + pointsAbs[0] + " " + pointsAbs[1] + " ");
	},

	/**
	 * linetoRel - l
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	linetoRel: function (x, y) {
		var pointsRel = this.editPointsRel([x, y]);

		this.d = this.d.concat(" l" + pointsRel[0] + " " + pointsRel[1] + " ");
	},

	/**
	 * movetoAbs - M
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	movetoAbs: function (x, y) {
		var pointsAbs = this.editPointsAbs([x, y]);

		this.d = this.d.concat(" M" + pointsAbs[0] + " " + pointsAbs[1] + " ");
	},

	/**
	 * movetoRel - m
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	movetoRel: function (x, y) {
		var pointsRel;
		if (this.d === "") {
			pointsRel = this.editPointsAbs([x, y]);
		} else {
			pointsRel = this.editPointsRel([x, y]);
		}

		this.d = this.d.concat(" m" + pointsRel[0] + " " + pointsRel[1] + " ");
	},

	/**
	 * curvetoQuadraticAbs - Q
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticAbs: function (x1, y1, x, y) {
		var pointsAbs = this.editPointsAbs([x1, y1, x, y]);

		this.d = this.d.concat(" Q" + pointsAbs[0] + " " + pointsAbs[1] + " " +
			pointsAbs[2] + " " + pointsAbs[3] + " ");
	},

	/**
	 * curvetoQuadraticRel - q
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticRel: function (x1, y1, x, y) {
		var pointsRel = this.editPointsRel([x1, y1, x, y]);

		this.d = this.d.concat(" q" + pointsRel[0] + " " + pointsRel[1] + " " +
			pointsRel[2] + " " + pointsRel[3] + " ");
	},

	/**
	 * curvetoCubicSmoothAbs - S
	 * 
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicSmoothAbs: function (x2, y2, x, y) {
		var pointsAbs = this.editPointsAbs([x2, y2, x, y]);

		this.d = this.d.concat(" S" + pointsAbs[0] + " " + pointsAbs[1] + " " +
			pointsAbs[2] + " " + pointsAbs[3] + " ");
	},

	/**
	 * curvetoCubicSmoothRel - s
	 * 
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicSmoothRel: function (x2, y2, x, y) {
		var pointsRel = this.editPointsRel([x2, y2, x, y]);

		this.d = this.d.concat(" s" + pointsRel[0] + " " + pointsRel[1] + " " +
			pointsRel[2] + " " + pointsRel[3] + " ");
	},

	/**
	 * curvetoQuadraticSmoothAbs - T
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticSmoothAbs: function (x, y) {
		var pointsAbs = this.editPointsAbs([x, y]);

		this.d = this.d.concat(" T" + pointsAbs[0] + " " + pointsAbs[1] + " ");
	},

	/**
	 * curvetoQuadraticSmoothRel - t
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticSmoothRel: function (x, y) {
		var pointsRel = this.editPointsRel([x, y]);

		this.d = this.d.concat(" t" + pointsRel[0] + " " + pointsRel[1] + " ");
	},

	/**
	 * linetoVerticalAbs - V
	 * 
	 * @param {Number} y
	 */
	linetoVerticalAbs: function (y) {
		var pointsAbs = this.editPointsAbs([0, y]);

		this.d = this.d.concat(" V" + pointsAbs[1] + " ");
	},

	/**
	 * linetoVerticalRel - v
	 * 
	 * @param {Number} y
	 */
	linetoVerticalRel: function (y) {
		var pointsRel = this.editPointsRel([0, y]);

		this.d = this.d.concat(" v" + pointsRel[1] + " ");
	},

	/**
	 * closePath - z or Z
	 */
	closePath: function () {
		this.d = this.d.concat(" z");
	}

});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.SVG) { ORYX.Core.SVG = {}; }


/**
 * MinMaxPathHandler
 * 
 * Determine the minimum and maximum of a SVG path's absolute coordinates.
 * For relative coordinates the absolute value is computed for consideration.
 * The values are stored in attributes minX, minY, maxX, and maxY.
 * 
 * @constructor
 */
ORYX.Core.SVG.MinMaxPathHandler = Clazz.extend({

	construct: function () {
		arguments.callee.$.construct.apply(this, arguments);

		this.minX = undefined;
		this.minY = undefined;
		this.maxX = undefined;
		this.maxY = undefined;

		this._lastAbsX = undefined;
		this._lastAbsY = undefined;
	},

	/**
	 * Store minimal and maximal coordinates of passed points to attributes minX, maxX, minY, maxY
	 * 
	 * @param {Array} points Array of absolutePoints
	 */
	calculateMinMax: function (points) {
		if (points instanceof Array) {
			var x, y;
			for (var i = 0; i < points.length; i++) {
				x = parseFloat(points[i]);
				i++;
				y = parseFloat(points[i]);

				this.minX = (this.minX !== undefined) ? Math.min(this.minX, x) : x;
				this.maxX = (this.maxX !== undefined) ? Math.max(this.maxX, x) : x;
				this.minY = (this.minY !== undefined) ? Math.min(this.minY, y) : y;
				this.maxY = (this.maxY !== undefined) ? Math.max(this.maxY, y) : y;

				this._lastAbsX = x;
				this._lastAbsY = y;
			}
		} else {
			//TODO error
		}
	},

	/**
	 * arcAbs - A
	 * 
	 * @param {Number} rx
	 * @param {Number} ry
	 * @param {Number} xAxisRotation
	 * @param {Boolean} largeArcFlag
	 * @param {Boolean} sweepFlag
	 * @param {Number} x
	 * @param {Number} y
	 */
	arcAbs: function (rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) {
		this.calculateMinMax([x, y]);
	},

	/**
	 * arcRel - a
	 * 
	 * @param {Number} rx
	 * @param {Number} ry
	 * @param {Number} xAxisRotation
	 * @param {Boolean} largeArcFlag
	 * @param {Boolean} sweepFlag
	 * @param {Number} x
	 * @param {Number} y
	 */
	arcRel: function (rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) {
		this.calculateMinMax([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * curvetoCubicAbs - C
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicAbs: function (x1, y1, x2, y2, x, y) {
		this.calculateMinMax([x1, y1, x2, y2, x, y]);
	},

	/**
	 * curvetoCubicRel - c
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicRel: function (x1, y1, x2, y2, x, y) {
		this.calculateMinMax([this._lastAbsX + x1, this._lastAbsY + y1,
		this._lastAbsX + x2, this._lastAbsY + y2,
		this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * linetoHorizontalAbs - H
	 * 
	 * @param {Number} x
	 */
	linetoHorizontalAbs: function (x) {
		this.calculateMinMax([x, this._lastAbsY]);
	},

	/**
	 * linetoHorizontalRel - h
	 * 
	 * @param {Number} x
	 */
	linetoHorizontalRel: function (x) {
		this.calculateMinMax([this._lastAbsX + x, this._lastAbsY]);
	},

	/**
	 * linetoAbs - L
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	linetoAbs: function (x, y) {
		this.calculateMinMax([x, y]);
	},

	/**
	 * linetoRel - l
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	linetoRel: function (x, y) {
		this.calculateMinMax([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * movetoAbs - M
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	movetoAbs: function (x, y) {
		this.calculateMinMax([x, y]);
	},

	/**
	 * movetoRel - m
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	movetoRel: function (x, y) {
		if (this._lastAbsX && this._lastAbsY) {
			this.calculateMinMax([this._lastAbsX + x, this._lastAbsY + y]);
		} else {
			this.calculateMinMax([x, y]);
		}
	},

	/**
	 * curvetoQuadraticAbs - Q
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticAbs: function (x1, y1, x, y) {
		this.calculateMinMax([x1, y1, x, y]);
	},

	/**
	 * curvetoQuadraticRel - q
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticRel: function (x1, y1, x, y) {
		this.calculateMinMax([this._lastAbsX + x1, this._lastAbsY + y1, this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * curvetoCubicSmoothAbs - S
	 * 
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicSmoothAbs: function (x2, y2, x, y) {
		this.calculateMinMax([x2, y2, x, y]);
	},

	/**
	 * curvetoCubicSmoothRel - s
	 * 
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicSmoothRel: function (x2, y2, x, y) {
		this.calculateMinMax([this._lastAbsX + x2, this._lastAbsY + y2, this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * curvetoQuadraticSmoothAbs - T
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticSmoothAbs: function (x, y) {
		this.calculateMinMax([x, y]);
	},

	/**
	 * curvetoQuadraticSmoothRel - t
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticSmoothRel: function (x, y) {
		this.calculateMinMax([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * linetoVerticalAbs - V
	 * 
	 * @param {Number} y
	 */
	linetoVerticalAbs: function (y) {
		this.calculateMinMax([this._lastAbsX, y]);
	},

	/**
	 * linetoVerticalRel - v
	 * 
	 * @param {Number} y
	 */
	linetoVerticalRel: function (y) {
		this.calculateMinMax([this._lastAbsX, this._lastAbsY + y]);
	},

	/**
	 * closePath - z or Z
	 */
	closePath: function () {
		return;// do nothing
	}

});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.SVG) { ORYX.Core.SVG = {}; }


/**
 * PathHandler
 * 
 * Determine absolute points of a SVG path. The coordinates are stored 
 * sequentially in the attribute points (x-coordinates at even indices,
 * y-coordinates at odd indices).
 * 
 * @constructor
 */
ORYX.Core.SVG.PointsPathHandler = Clazz.extend({

	construct: function () {
		arguments.callee.$.construct.apply(this, arguments);

		this.points = [];

		this._lastAbsX = undefined;
		this._lastAbsY = undefined;
	},

	/**
	 * addPoints
	 * 
	 * @param {Array} points Array of absolutePoints
	 */
	addPoints: function (points) {
		if (points instanceof Array) {
			var x, y;
			for (var i = 0; i < points.length; i++) {
				x = parseFloat(points[i]);
				i++;
				y = parseFloat(points[i]);

				this.points.push(x);
				this.points.push(y);
				//this.points.push({x:x, y:y});

				this._lastAbsX = x;
				this._lastAbsY = y;
			}
		} else {
			//TODO error
		}
	},

	/**
	 * arcAbs - A
	 * 
	 * @param {Number} rx
	 * @param {Number} ry
	 * @param {Number} xAxisRotation
	 * @param {Boolean} largeArcFlag
	 * @param {Boolean} sweepFlag
	 * @param {Number} x
	 * @param {Number} y
	 */
	arcAbs: function (rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) {
		this.addPoints([x, y]);
	},

	/**
	 * arcRel - a
	 * 
	 * @param {Number} rx
	 * @param {Number} ry
	 * @param {Number} xAxisRotation
	 * @param {Boolean} largeArcFlag
	 * @param {Boolean} sweepFlag
	 * @param {Number} x
	 * @param {Number} y
	 */
	arcRel: function (rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) {
		this.addPoints([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * curvetoCubicAbs - C
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicAbs: function (x1, y1, x2, y2, x, y) {
		this.addPoints([x, y]);
	},

	/**
	 * curvetoCubicRel - c
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicRel: function (x1, y1, x2, y2, x, y) {
		this.addPoints([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * linetoHorizontalAbs - H
	 * 
	 * @param {Number} x
	 */
	linetoHorizontalAbs: function (x) {
		this.addPoints([x, this._lastAbsY]);
	},

	/**
	 * linetoHorizontalRel - h
	 * 
	 * @param {Number} x
	 */
	linetoHorizontalRel: function (x) {
		this.addPoints([this._lastAbsX + x, this._lastAbsY]);
	},

	/**
	 * linetoAbs - L
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	linetoAbs: function (x, y) {
		this.addPoints([x, y]);
	},

	/**
	 * linetoRel - l
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	linetoRel: function (x, y) {
		this.addPoints([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * movetoAbs - M
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	movetoAbs: function (x, y) {
		this.addPoints([x, y]);
	},

	/**
	 * movetoRel - m
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	movetoRel: function (x, y) {
		if (this._lastAbsX && this._lastAbsY) {
			this.addPoints([this._lastAbsX + x, this._lastAbsY + y]);
		} else {
			this.addPoints([x, y]);
		}
	},

	/**
	 * curvetoQuadraticAbs - Q
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticAbs: function (x1, y1, x, y) {
		this.addPoints([x, y]);
	},

	/**
	 * curvetoQuadraticRel - q
	 * 
	 * @param {Number} x1
	 * @param {Number} y1
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticRel: function (x1, y1, x, y) {
		this.addPoints([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * curvetoCubicSmoothAbs - S
	 * 
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicSmoothAbs: function (x2, y2, x, y) {
		this.addPoints([x, y]);
	},

	/**
	 * curvetoCubicSmoothRel - s
	 * 
	 * @param {Number} x2
	 * @param {Number} y2
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoCubicSmoothRel: function (x2, y2, x, y) {
		this.addPoints([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * curvetoQuadraticSmoothAbs - T
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticSmoothAbs: function (x, y) {
		this.addPoints([x, y]);
	},

	/**
	 * curvetoQuadraticSmoothRel - t
	 * 
	 * @param {Number} x
	 * @param {Number} y
	 */
	curvetoQuadraticSmoothRel: function (x, y) {
		this.addPoints([this._lastAbsX + x, this._lastAbsY + y]);
	},

	/**
	 * linetoVerticalAbs - V
	 * 
	 * @param {Number} y
	 */
	linetoVerticalAbs: function (y) {
		this.addPoints([this._lastAbsX, y]);
	},

	/**
	 * linetoVerticalRel - v
	 * 
	 * @param {Number} y
	 */
	linetoVerticalRel: function (y) {
		this.addPoints([this._lastAbsX, this._lastAbsY + y]);
	},

	/**
	 * closePath - z or Z
	 */
	closePath: function () {
		return;// do nothing
	}

});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 *
 * Config variables
 */
NAMESPACE_ORYX = "http://www.b3mn.org/oryx";
NAMESPACE_SVG = "http://www.w3.org/2000/svg/";

/**
 * @classDescription This class wraps the manipulation of a SVG marker.
 * @namespace ORYX.Core.SVG
 * uses Inheritance (Clazz)
 * uses Prototype 1.5.0
 *
 */

/**
 * Init package
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.SVG) { ORYX.Core.SVG = {}; }

ORYX.Core.SVG.SVGMarker = Clazz.extend({

	/**
	 * Constructor
	 * @param markerElement {SVGMarkerElement}
	 */
	construct: function (markerElement) {
		arguments.callee.$.construct.apply(this, arguments);

		this.id = undefined;
		this.element = markerElement;
		this.refX = undefined;
		this.refY = undefined;
		this.markerWidth = undefined;
		this.markerHeight = undefined;
		this.oldRefX = undefined;
		this.oldRefY = undefined;
		this.oldMarkerWidth = undefined;
		this.oldMarkerHeight = undefined;
		this.optional = false;
		this.enabled = true;
		this.minimumLength = undefined;
		this.resize = false;

		this.svgShapes = [];

		this._init(); //initialisation of all the properties declared above.
	},

	/**
	 * Initializes the values that are defined in the constructor.
	 */
	_init: function () {
		//check if this.element is a SVGMarkerElement
		if (!(this.element == "[object SVGMarkerElement]")) {
			throw "SVGMarker: Argument is not an instance of SVGMarkerElement.";
		}

		this.id = this.element.getAttributeNS(null, "id");

		//init svg marker attributes
		var refXValue = this.element.getAttributeNS(null, "refX");
		if (refXValue) {
			this.refX = parseFloat(refXValue);
		} else {
			this.refX = 0;
		}
		var refYValue = this.element.getAttributeNS(null, "refY");
		if (refYValue) {
			this.refY = parseFloat(refYValue);
		} else {
			this.refY = 0;
		}
		var markerWidthValue = this.element.getAttributeNS(null, "markerWidth");
		if (markerWidthValue) {
			this.markerWidth = parseFloat(markerWidthValue);
		} else {
			this.markerWidth = 3;
		}
		var markerHeightValue = this.element.getAttributeNS(null, "markerHeight");
		if (markerHeightValue) {
			this.markerHeight = parseFloat(markerHeightValue);
		} else {
			this.markerHeight = 3;
		}

		this.oldRefX = this.refX;
		this.oldRefY = this.refY;
		this.oldMarkerWidth = this.markerWidth;
		this.oldMarkerHeight = this.markerHeight;

		//init oryx attributes
		var optionalAttr = this.element.getAttributeNS(NAMESPACE_ORYX, "optional");
		if (optionalAttr) {
			optionalAttr = optionalAttr.strip();
			this.optional = (optionalAttr.toLowerCase() === "yes");
		} else {
			this.optional = false;
		}

		var enabledAttr = this.element.getAttributeNS(NAMESPACE_ORYX, "enabled");
		if (enabledAttr) {
			enabledAttr = enabledAttr.strip();
			this.enabled = !(enabledAttr.toLowerCase() === "no");
		} else {
			this.enabled = true;
		}

		var minLengthAttr = this.element.getAttributeNS(NAMESPACE_ORYX, "minimumLength");
		if (minLengthAttr) {
			this.minimumLength = parseFloat(minLengthAttr);
		}

		var resizeAttr = this.element.getAttributeNS(NAMESPACE_ORYX, "resize");
		if (resizeAttr) {
			resizeAttr = resizeAttr.strip();
			this.resize = (resizeAttr.toLowerCase() === "yes");
		} else {
			this.resize = false;
		}

		//init SVGShape objects
		//this.svgShapes = this._getSVGShapes(this.element);
	},

	/**
	 *
	 */
	_getSVGShapes: function (svgElement) {
		if (svgElement.hasChildNodes) {
			var svgShapes = [];
			var me = this;
			$A(svgElement.childNodes).each(function (svgChild) {
				try {
					var svgShape = new ORYX.Core.SVG.SVGShape(svgChild);
					svgShapes.push(svgShape);
				} catch (e) {
					svgShapes = svgShapes.concat(me._getSVGShapes(svgChild));
				}
			});
			return svgShapes;
		}
	},

	/**
	 * Writes the changed values into the SVG marker.
	 */
	update: function () {
		//TODO mache marker resizebar!!! aber erst wenn der rest der connectingshape funzt!
		/**
		// //update marker attributes
		// if(this.refX != this.oldRefX) {
		// 	this.element.setAttributeNS(null, "refX", this.refX);
		// }
		// if(this.refY != this.oldRefY) {
		// 	this.element.setAttributeNS(null, "refY", this.refY);
		// }
		// if(this.markerWidth != this.oldMarkerWidth) {
		// 	this.element.setAttributeNS(null, "markerWidth", this.markerWidth);
		// }
		// if(this.markerHeight != this.oldMarkerHeight) {
		// 	this.element.setAttributeNS(null, "markerHeight", this.markerHeight);
		// }
		
		// //update SVGShape objects
		// var widthDelta = this.markerWidth / this.oldMarkerWidth;
		// var heightDelta = this.markerHeight / this.oldMarkerHeight;
		// if(widthDelta != 1 && heightDelta != 1) {
		// 	this.svgShapes.each(function(svgShape) {

		// 	});
		// }
		 */
		//update old values to prepare the next update
		this.oldRefX = this.refX;
		this.oldRefY = this.refY;
		this.oldMarkerWidth = this.markerWidth;
		this.oldMarkerHeight = this.markerHeight;
	},

	toString: function () { return (this.element) ? "SVGMarker " + this.element.id : "SVGMarker " + this.element; }
});
/**
* Copyright (c) 2006
* Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
**/

/**
 *
 * Config variables
 */
NAMESPACE_ORYX = "http://www.b3mn.org/oryx";
NAMESPACE_SVG = "http://www.w3.org/2000/svg/";

/**
 * @classDescription This class wraps the manipulation of a SVG basic shape or a path.
 * @namespace ORYX.Core.SVG
 * uses Inheritance (Clazz)
 * uses Prototype 1.5.0
 * uses PathParser by Kevin Lindsey (http://kevlindev.com/)
 * uses MinMaxPathHandler
 * uses EditPathHandler
 *
 */

//init package
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.SVG) { ORYX.Core.SVG = {}; }

ORYX.Core.SVG.SVGShape = Clazz.extend({

	/**
	 * Constructor
	 * @param svgElem {SVGElement} An SVGElement that is a basic shape or a path.
	 */
	construct: function (svgElem) {
		arguments.callee.$.construct.apply(this, arguments);

		this.type;
		this.element = svgElem;
		this.x = undefined;
		this.y = undefined;
		this.width = undefined;
		this.height = undefined;
		this.oldX = undefined;
		this.oldY = undefined;
		this.oldWidth = undefined;
		this.oldHeight = undefined;
		this.radiusX = undefined;
		this.radiusY = undefined;
		this.isHorizontallyResizable = false;
		this.isVerticallyResizable = false;
		//this.anchors = [];
		this.anchorLeft = false;
		this.anchorRight = false;
		this.anchorTop = false;
		this.anchorBottom = false;

		//attributes of path elements of edge objects
		this.allowDockers = true;
		this.resizeMarkerMid = false;

		this.editPathParser;
		this.editPathHandler;

		this.init(); //initialisation of all the properties declared above.
	},

	/**
	 * Initializes the values that are defined in the constructor.
	 */
	init: function () {

		/**initialize position and size*/
		if (ORYX.Editor.checkClassType(this.element, SVGRectElement) || ORYX.Editor.checkClassType(this.element, SVGImageElement)) {
			this.type = "Rect";

			var xAttr = this.element.getAttributeNS(null, "x");
			if (xAttr) {
				this.oldX = parseFloat(xAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var yAttr = this.element.getAttributeNS(null, "y");
			if (yAttr) {
				this.oldY = parseFloat(yAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var widthAttr = this.element.getAttributeNS(null, "width");
			if (widthAttr) {
				this.oldWidth = parseFloat(widthAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var heightAttr = this.element.getAttributeNS(null, "height");
			if (heightAttr) {
				this.oldHeight = parseFloat(heightAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}

		} else if (ORYX.Editor.checkClassType(this.element, SVGCircleElement)) {
			this.type = "Circle";

			var cx = undefined;
			var cy = undefined;
			//var r = undefined;

			var cxAttr = this.element.getAttributeNS(null, "cx");
			if (cxAttr) {
				cx = parseFloat(cxAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var cyAttr = this.element.getAttributeNS(null, "cy");
			if (cyAttr) {
				cy = parseFloat(cyAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var rAttr = this.element.getAttributeNS(null, "r");
			if (rAttr) {
				//r = parseFloat(rAttr);
				this.radiusX = parseFloat(rAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			this.oldX = cx - this.radiusX;
			this.oldY = cy - this.radiusX;
			this.oldWidth = 2 * this.radiusX;
			this.oldHeight = 2 * this.radiusX;

		} else if (ORYX.Editor.checkClassType(this.element, SVGEllipseElement)) {
			this.type = "Ellipse";

			var cx = undefined;
			var cy = undefined;
			//var rx = undefined;
			//var ry = undefined;
			var cxAttr = this.element.getAttributeNS(null, "cx");
			if (cxAttr) {
				cx = parseFloat(cxAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var cyAttr = this.element.getAttributeNS(null, "cy");
			if (cyAttr) {
				cy = parseFloat(cyAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var rxAttr = this.element.getAttributeNS(null, "rx");
			if (rxAttr) {
				this.radiusX = parseFloat(rxAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var ryAttr = this.element.getAttributeNS(null, "ry");
			if (ryAttr) {
				this.radiusY = parseFloat(ryAttr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			this.oldX = cx - this.radiusX;
			this.oldY = cy - this.radiusY;
			this.oldWidth = 2 * this.radiusX;
			this.oldHeight = 2 * this.radiusY;

		} else if (ORYX.Editor.checkClassType(this.element, SVGLineElement)) {
			this.type = "Line";

			var x1 = undefined;
			var y1 = undefined;
			var x2 = undefined;
			var y2 = undefined;
			var x1Attr = this.element.getAttributeNS(null, "x1");
			if (x1Attr) {
				x1 = parseFloat(x1Attr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var y1Attr = this.element.getAttributeNS(null, "y1");
			if (y1Attr) {
				y1 = parseFloat(y1Attr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var x2Attr = this.element.getAttributeNS(null, "x2");
			if (x2Attr) {
				x2 = parseFloat(x2Attr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			var y2Attr = this.element.getAttributeNS(null, "y2");
			if (y2Attr) {
				y2 = parseFloat(y2Attr);
			} else {
				throw "Missing attribute in element " + this.element;
			}
			this.oldX = Math.min(x1, x2);
			this.oldY = Math.min(y1, y2);
			this.oldWidth = Math.abs(x1 - x2);
			this.oldHeight = Math.abs(y1 - y2);

		} else if (ORYX.Editor.checkClassType(this.element, SVGPolylineElement) || ORYX.Editor.checkClassType(this.element, SVGPolygonElement)) {
			this.type = "Polyline";

			var pointsArray = [];
			if (this.element.points && this.element.points.numberOfItems) {
				for (var i = 0, size = this.element.points.numberOfItems; i < size; i++) {
					pointsArray.push(this.element.points.getItem(i).x)
					pointsArray.push(this.element.points.getItem(i).y)
				}
			} else {
				var points = this.element.getAttributeNS(null, "points");
				if (points) {
					points = points.replace(/,/g, " ");
					pointsArray = points.split(" ");
					pointsArray = pointsArray.without("");
				} else {
					throw "Missing attribute in element " + this.element;
				}
			}


			if (pointsArray && pointsArray.length && pointsArray.length > 1) {
				var minX = parseFloat(pointsArray[0]);
				var minY = parseFloat(pointsArray[1]);
				var maxX = parseFloat(pointsArray[0]);
				var maxY = parseFloat(pointsArray[1]);

				for (var i = 0; i < pointsArray.length; i++) {
					minX = Math.min(minX, parseFloat(pointsArray[i]));
					maxX = Math.max(maxX, parseFloat(pointsArray[i]));
					i++;
					minY = Math.min(minY, parseFloat(pointsArray[i]));
					maxY = Math.max(maxY, parseFloat(pointsArray[i]));
				}

				this.oldX = minX;
				this.oldY = minY;
				this.oldWidth = maxX - minX;
				this.oldHeight = maxY - minY;
			} else {
				throw "Missing attribute in element " + this.element;
			}

		} else if (ORYX.Editor.checkClassType(this.element, SVGPathElement)) {
			this.type = "Path";

			this.editPathParser = new PathParser();
			this.editPathHandler = new ORYX.Core.SVG.EditPathHandler();
			this.editPathParser.setHandler(this.editPathHandler);

			var parser = new PathParser();
			var handler = new ORYX.Core.SVG.MinMaxPathHandler();
			parser.setHandler(handler);
			parser.parsePath(this.element);

			this.oldX = handler.minX;
			this.oldY = handler.minY;
			this.oldWidth = handler.maxX - handler.minX;
			this.oldHeight = handler.maxY - handler.minY;

			delete parser;
			delete handler;
		} else {
			throw "Element is not a shape.";
		}

		/** initialize attributes of oryx namespace */
		//resize
		var resizeAttr = this.element.getAttributeNS(NAMESPACE_ORYX, "resize");
		if (resizeAttr) {
			resizeAttr = resizeAttr.toLowerCase();
			if (resizeAttr.match(/horizontal/)) {
				this.isHorizontallyResizable = true;
			} else {
				this.isHorizontallyResizable = false;
			}
			if (resizeAttr.match(/vertical/)) {
				this.isVerticallyResizable = true;
			} else {
				this.isVerticallyResizable = false;
			}
		} else {
			this.isHorizontallyResizable = false;
			this.isVerticallyResizable = false;
		}

		//anchors
		var anchorAttr = this.element.getAttributeNS(NAMESPACE_ORYX, "anchors");
		if (anchorAttr) {
			anchorAttr = anchorAttr.replace("/,/g", " ");
			var anchors = anchorAttr.split(" ").without("");

			for (var i = 0; i < anchors.length; i++) {
				switch (anchors[i].toLowerCase()) {
					case "left":
						this.anchorLeft = true;
						break;
					case "right":
						this.anchorRight = true;
						break;
					case "top":
						this.anchorTop = true;
						break;
					case "bottom":
						this.anchorBottom = true;
						break;
				}
			}
		}

		//allowDockers and resizeMarkerMid
		if (ORYX.Editor.checkClassType(this.element, SVGPathElement)) {
			var allowDockersAttr = this.element.getAttributeNS(NAMESPACE_ORYX, "allowDockers");
			if (allowDockersAttr) {
				if (allowDockersAttr.toLowerCase() === "no") {
					this.allowDockers = false;
				} else {
					this.allowDockers = true;
				}
			}

			var resizeMarkerMidAttr = this.element.getAttributeNS(NAMESPACE_ORYX, "resizeMarker-mid");
			if (resizeMarkerMidAttr) {
				if (resizeMarkerMidAttr.toLowerCase() === "yes") {
					this.resizeMarkerMid = true;
				} else {
					this.resizeMarkerMid = false;
				}
			}
		}

		this.x = this.oldX;
		this.y = this.oldY;
		this.width = this.oldWidth;
		this.height = this.oldHeight;
	},

	/**
	 * Writes the changed values into the SVG element.
	 */
	update: function () {

		if (this.x !== this.oldX || this.y !== this.oldY || this.width !== this.oldWidth || this.height !== this.oldHeight) {
			switch (this.type) {
				case "Rect":
					if (this.x !== this.oldX) this.element.setAttributeNS(null, "x", this.x);
					if (this.y !== this.oldY) this.element.setAttributeNS(null, "y", this.y);
					if (this.width !== this.oldWidth) this.element.setAttributeNS(null, "width", this.width);
					if (this.height !== this.oldHeight) this.element.setAttributeNS(null, "height", this.height);
					break;
				case "Circle":
					this.radiusX = ((this.width < this.height) ? this.width : this.height) / 2.0;

					this.element.setAttributeNS(null, "cx", this.x + this.width / 2.0);
					this.element.setAttributeNS(null, "cy", this.y + this.height / 2.0);
					this.element.setAttributeNS(null, "r", this.radiusX);
					break;
				case "Ellipse":
					this.radiusX = this.width / 2;
					this.radiusY = this.height / 2;

					this.element.setAttributeNS(null, "cx", this.x + this.radiusX);
					this.element.setAttributeNS(null, "cy", this.y + this.radiusY);
					this.element.setAttributeNS(null, "rx", this.radiusX);
					this.element.setAttributeNS(null, "ry", this.radiusY);
					break;
				case "Line":
					if (this.x !== this.oldX)
						this.element.setAttributeNS(null, "x1", this.x);

					if (this.y !== this.oldY)
						this.element.setAttributeNS(null, "y1", this.y);

					if (this.x !== this.oldX || this.width !== this.oldWidth)
						this.element.setAttributeNS(null, "x2", this.x + this.width);

					if (this.y !== this.oldY || this.height !== this.oldHeight)
						this.element.setAttributeNS(null, "y2", this.y + this.height);
					break;
				case "Polyline":
					var points = this.element.getAttributeNS(null, "points");
					if (points) {
						points = points.replace(/,/g, " ").split(" ").without("");

						if (points && points.length && points.length > 1) {

							//TODO what if oldWidth == 0?
							var widthDelta = (this.oldWidth === 0) ? 0 : this.width / this.oldWidth;
							var heightDelta = (this.oldHeight === 0) ? 0 : this.height / this.oldHeight;

							var updatedPoints = "";
							for (var i = 0; i < points.length; i++) {
								var x = (parseFloat(points[i]) - this.oldX) * widthDelta + this.x;
								i++;
								var y = (parseFloat(points[i]) - this.oldY) * heightDelta + this.y;
								updatedPoints += x + " " + y + " ";
							}
							this.element.setAttributeNS(null, "points", updatedPoints);
						} else {
							//TODO error
						}
					} else {
						//TODO error
					}
					break;
				case "Path":
					//calculate scaling delta
					//TODO what if oldWidth == 0?
					var widthDelta = (this.oldWidth === 0) ? 0 : this.width / this.oldWidth;
					var heightDelta = (this.oldHeight === 0) ? 0 : this.height / this.oldHeight;

					//use path parser to edit each point of the path
					this.editPathHandler.init(this.x, this.y, this.oldX, this.oldY, widthDelta, heightDelta);
					this.editPathParser.parsePath(this.element);

					//change d attribute of path
					this.element.setAttributeNS(null, "d", this.editPathHandler.d);
					break;
			}

			this.oldX = this.x;
			this.oldY = this.y;
			this.oldWidth = this.width;
			this.oldHeight = this.height;
		}

		// Remove cached variables
		delete this.visible;
		delete this.handler;
	},

	isPointIncluded: function (pointX, pointY) {

		// Check if there are the right arguments and if the node is visible
		if (!pointX || !pointY || !this.isVisible()) {
			return false;
		}

		switch (this.type) {
			case "Rect":
				return (pointX >= this.x && pointX <= this.x + this.width &&
					pointY >= this.y && pointY <= this.y + this.height);
				break;
			case "Circle":
				//calculate the radius
				return ORYX.Core.Math.isPointInEllipse(pointX, pointY, this.x + this.width / 2.0, this.y + this.height / 2.0, this.radiusX, this.radiusX);
				break;
			case "Ellipse":
				return ORYX.Core.Math.isPointInEllipse(pointX, pointY, this.x + this.radiusX, this.y + this.radiusY, this.radiusX, this.radiusY);
				break;
			case "Line":
				return ORYX.Core.Math.isPointInLine(pointX, pointY, this.x, this.y, this.x + this.width, this.y + this.height);
				break;
			case "Polyline":
				var points = this.element.getAttributeNS(null, "points");

				if (points) {
					points = points.replace(/,/g, " ").split(" ").without("");

					points = points.collect(function (n) {
						return parseFloat(n);
					});

					return ORYX.Core.Math.isPointInPolygone(pointX, pointY, points);
				} else {
					return false;
				}
				break;
			case "Path":

				// Cache Path handler
				if (!this.handler) {
					var parser = new PathParser();
					this.handler = new ORYX.Core.SVG.PointsPathHandler();
					parser.setHandler(this.handler);
					parser.parsePath(this.element);
				}

				return ORYX.Core.Math.isPointInPolygone(pointX, pointY, this.handler.points);

				break;
			default:
				return false;
		}
	},

	/**
	 * Returns true if the element is visible
	 * @param {SVGElement} elem
	 * @return boolean
	 */
	isVisible: function (elem) {

		if (this.visible !== undefined) {
			return this.visible;
		}

		if (!elem) {
			elem = this.element;
		}

		var hasOwnerSVG = false;
		try {
			hasOwnerSVG = !!elem.ownerSVGElement;
		} catch (e) { }

		// Is SVG context
		if (hasOwnerSVG) {
			// IF G-Element
			if (ORYX.Editor.checkClassType(elem, SVGGElement)) {
				if (elem.className && elem.className.baseVal == "me") {
					this.visible = true;
					return this.visible;
				}
			}

			// Check if fill or stroke is set
			var fill = elem.getAttributeNS(null, "fill");
			var stroke = elem.getAttributeNS(null, "stroke");
			if (fill && fill == "none" && stroke && stroke == "none") {
				this.visible = false;
			} else {
				// Check if displayed
				var attr = elem.getAttributeNS(null, "display");
				if (!attr)
					this.visible = this.isVisible(elem.parentNode);
				else if (attr == "none")
					this.visible = false;
				else
					this.visible = true;
			}
		} else {
			this.visible = true;
		}

		return this.visible;
	},

	toString: function () { return (this.element) ? "SVGShape " + this.element.id : "SVGShape " + this.element; }
});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.SVG) { ORYX.Core.SVG = {}; }

/**
 * @classDescription Class for adding text to a shape.
 * 
 */
ORYX.Core.SVG.Label = Clazz.extend({

	_characterSets: [
		"%W",
		"@",
		"m",
		"wDGMOQÖ#+=<>~^",
		"ABCHKNRSUVXZÜÄ&",
		"bdghnopquxöüETY1234567890ß_§${}*´`µ€",
		"aeksvyzäFLP?°²³",
		"c-",
		"rtJ\"/()[]:;!|\\",
		"fjI., ",
		"'",
		"il"
	],
	_characterSetValues: [15, 14, 13, 11, 10, 9, 8, 7, 6, 5, 4, 3],

	/**
	 * Constructor
	 * @param options {Object} :
	 * 	textElement
	 * 
	 */
	construct: function (options) {
		arguments.callee.$.construct.apply(this, arguments);

		if (!options.textElement) {
			throw "Label: No parameter textElement."
		} else if (!ORYX.Editor.checkClassType(options.textElement, SVGTextElement)) {
			throw "Label: Parameter textElement is not an SVGTextElement."
		}

		this.invisibleRenderPoint = -5000;

		this.node = options.textElement;


		this.node.setAttributeNS(null, 'stroke-width', '0pt');
		this.node.setAttributeNS(null, 'letter-spacing', '-0.01px');

		this.shapeId = options.shapeId;

		this.id;

		this.fitToElemId;

		this.edgePosition;

		this.x;
		this.y;
		this.oldX;
		this.oldY;

		this.isVisible = true;

		this._text;
		this._verticalAlign;
		this._horizontalAlign;
		this._rotate;
		this._rotationPoint;

		//this.anchors = [];
		this.anchorLeft;
		this.anchorRight;
		this.anchorTop;
		this.anchorBottom;

		this._isChanged = true;

		//if the text element already has an id, don't change it.
		var _id = this.node.getAttributeNS(null, 'id');
		if (_id) {
			this.id = _id;
		}

		//initialization	

		//set referenced element the text is fit to
		this.fitToElemId = this.node.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, 'fittoelem');
		if (this.fitToElemId)
			this.fitToElemId = this.shapeId + this.fitToElemId;

		//set alignment	
		var alignValues = this.node.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, 'align');
		if (alignValues) {
			alignValues = alignValues.replace(/,/g, " ");
			alignValues = alignValues.split(" ");
			alignValues = alignValues.without("");

			alignValues.each((function (alignValue) {
				switch (alignValue) {
					case 'top':
					case 'middle':
					case 'bottom':
						if (!this._verticalAlign) { this._originVerticalAlign = this._verticalAlign = alignValue; }
						break;
					case 'left':
					case 'center':
					case 'right':
						if (!this._horizontalAlign) { this._originHorizontalAlign = this._horizontalAlign = alignValue; }
						break;
				}
			}).bind(this));
		}

		//set edge position (only in case the label belongs to an edge)
		this.edgePosition = this.node.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, 'edgePosition');
		if (this.edgePosition) {
			this.originEdgePosition = this.edgePosition = this.edgePosition.toLowerCase();
		}


		//get offset top
		this.offsetTop = this.node.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, 'offsetTop') || ORYX.CONFIG.OFFSET_EDGE_LABEL_TOP;
		if (this.offsetTop) {
			this.offsetTop = parseInt(this.offsetTop);
		}

		//get offset top
		this.offsetBottom = this.node.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, 'offsetBottom') || ORYX.CONFIG.OFFSET_EDGE_LABEL_BOTTOM;
		if (this.offsetBottom) {
			this.offsetBottom = parseInt(this.offsetBottom);
		}


		//set rotation
		var rotateValue = this.node.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, 'rotate');
		if (rotateValue) {
			try {
				this._rotate = parseFloat(rotateValue);
			} catch (e) {
				this._rotate = 0;
			}
		} else {
			this._rotate = 0;
		}

		//anchors
		var anchorAttr = this.node.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "anchors");
		if (anchorAttr) {
			anchorAttr = anchorAttr.replace("/,/g", " ");
			var anchors = anchorAttr.split(" ").without("");

			for (var i = 0; i < anchors.length; i++) {
				switch (anchors[i].toLowerCase()) {
					case "left":
						this.originAnchorLeft = this.anchorLeft = true;
						break;
					case "right":
						this.originAnchorRight = this.anchorRight = true;
						break;
					case "top":
						this.originAnchorTop = this.anchorTop = true;
						break;
					case "bottom":
						this.originAnchorBottom = this.anchorBottom = true;
						break;
				}
			}
		}

		//if no alignment defined, set default alignment
		if (!this._verticalAlign) { this._verticalAlign = 'bottom'; }
		if (!this._horizontalAlign) { this._horizontalAlign = 'left'; }

		var xValue = this.node.getAttributeNS(null, 'x');
		if (xValue) {
			this.oldX = this.x = parseFloat(xValue);
		} else {
			//TODO error
		}

		var yValue = this.node.getAttributeNS(null, 'y');
		if (yValue) {
			this.oldY = this.y = parseFloat(yValue);
		} else {
			//TODO error
		}

		//set initial text
		this.text(this.node.textContent);
	},

	/**
	 * Reset the anchor position to the original value
	 * which was specified in the stencil set
	 * 
	 */
	resetAnchorPosition: function () {
		this.anchorLeft = this.originAnchorLeft || false;
		this.anchorRight = this.originAnchorRight || false;
		this.anchorTop = this.originAnchorTop || false;
		this.anchorBottom = this.originAnchorBottom || false;
	},

	isOriginAnchorLeft: function () { return this.originAnchorLeft || false; },
	isOriginAnchorRight: function () { return this.originAnchorRight || false; },
	isOriginAnchorTop: function () { return this.originAnchorTop || false; },
	isOriginAnchorBottom: function () { return this.originAnchorBottom || false; },


	isAnchorLeft: function () { return this.anchorLeft || false; },
	isAnchorRight: function () { return this.anchorRight || false; },
	isAnchorTop: function () { return this.anchorTop || false; },
	isAnchorBottom: function () { return this.anchorBottom || false; },

	/**
	 * Returns the x coordinate
	 * @return {number}
	 */
	getX: function () {
		try {
			var x = this.node.x.baseVal.getItem(0).value;
			switch (this.horizontalAlign()) {
				case "left": return x;
				case "center": return x - (this.getWidth() / 2);
				case "right": return x - this.getWidth();
			}
			return this.node.getBBox().x;
		} catch (e) {
			return this.x;
		}
	},

	setX: function (x) {
		if (this.position)
			this.position.x = x;
		else
			this.setOriginX(x);
	},


	/**
	 * Returns the y coordinate
	 * @return {number}
	 */
	getY: function () {
		try {
			return this.node.getBBox().y;
		} catch (e) {
			return this.y;
		}
	},

	setY: function (y) {
		if (this.position)
			this.position.y = y;
		else
			this.setOriginY(y);
	},

	setOriginX: function (x) {
		this.x = x;
	},

	setOriginY: function (y) {
		this.y = y;
	},


	/**
	 * Returns the width of the label
	 * @return {number}
	 */
	getWidth: function () {
		try {
			try {
				var width, cn = this.node.childNodes;
				if (cn.length == 0 || !Ext.isGecko) {
					width = this.node.getBBox().width;
				} else {
					for (var i = 0, size = cn.length; i < size; ++i) {
						var w = cn[i].getComputedTextLength();
						if ("undefined" == typeof width || width < w) {
							width = w;
						}
					}
				}
				return width + (width % 2 == 0 ? 0 : 1);
			} catch (ee) {
				return this.node.getBBox().width;
			}
		} catch (e) {
			return 0;
		}
	},

	getOriginUpperLeft: function () {
		var x = this.x, y = this.y;
		switch (this._horizontalAlign) {
			case 'center':
				x -= this.getWidth() / 2;
				break;
			case 'right':
				x -= this.getWidth();
				break;
		}
		switch (this._verticalAlign) {
			case 'middle':
				y -= this.getHeight() / 2;
				break;
			case 'bottom':
				y -= this.getHeight();
				break;
		}
		return { x: x, y: y };
	},

	/**
	 * Returns the height of the label
	 * @return {number}
	 */
	getHeight: function () {
		try {
			return this.node.getBBox().height;
		} catch (e) {
			return 0;
		}
	},

	/**
	 * Returns the relative center position of the label 
	 * to its parent shape.
	 * @return {Object}
	 */
	getCenter: function () {
		var up = { x: this.getX(), y: this.getY() };
		up.x += this.getWidth() / 2;
		up.y += this.getHeight() / 2;
		return up;
	},

	/**
	 * Sets the position of a label relative to the parent.
	 * @param {Object} position
	 */
	setPosition: function (position) {
		if (!position || position.x === undefined || position.y === undefined) {
			delete this.position;
		} else {
			this.position = position;
		}

		if (this.position) {
			delete this._referencePoint;
			delete this.edgePosition;
		}

		this._isChanged = true;
		this.update();
	},

	/**
	 * Return the position
	 */
	getPosition: function () {
		return this.position;
	},

	setReferencePoint: function (ref) {
		if (ref) {
			this._referencePoint = ref;
		} else {
			delete this._referencePoint;
		}
		if (this._referencePoint) {
			delete this.position;
		}
	},

	getReferencePoint: function () {
		return this._referencePoint || undefined;
	},

	changed: function () {
		this._isChanged = true;
	},

	/**
	 * Register a callback which will be called if the label
	 * was rendered.
	 * @param {Object} fn
	 */
	registerOnChange: function (fn) {
		if (!this.changeCallbacks) {
			this.changeCallbacks = [];
		}
		if (fn instanceof Function && !this.changeCallbacks.include(fn)) {
			this.changeCallbacks.push(fn);
		}
	},

	/**
	 * Unregister the callback for changes.
	 * @param {Object} fn
	 */
	unregisterOnChange: function (fn) {
		if (this.changeCallbacks && fn instanceof Function && this.changeCallbacks.include(fn)) {
			this.changeCallbacks = this.changeCallbacks.without(fn);
		}
	},

	/**
	 * Returns TRUE if the labe is currently in
	 * the update mechanism.
	 * @return {Boolean}
	 */
	isUpdating: function () {
		return !!this._isUpdating;
	},


	getOriginEdgePosition: function () {
		return this.originEdgePosition;
	},

	/**
	 * Returns the edgeposition.
	 * 
	 * @return {String} "starttop", "startmiddle", "startbottom", 
	 * "midtop", "midbottom", "endtop", "endbottom" or null
	 */
	getEdgePosition: function () {
		return this.edgePosition || null;
	},

	/**
	 * Set the edge position, must be one of the valid
	 * edge positions (see getEdgePosition).
	 * Removes the reference point and the absolute position as well.
	 * 
	 * @param {Object} position
	 */
	setEdgePosition: function (position) {
		if (["starttop", "startmiddle", "startbottom",
			"midtop", "midbottom", "endtop", "endbottom"].include(position)) {
			this.edgePosition = position;
			delete this.position;
			delete this._referencePoint;
		} else {
			delete this.edgePosition;
		}
	},

	/**
	 * Update the SVG text element.
	 */
	update: function (force) {

		var x = this.x, y = this.y;
		if (this.position) {
			x = this.position.x;
			y = this.position.y;
		}
		x = Math.floor(x); y = Math.floor(y);

		if (this._isChanged || x !== this.oldX || y !== this.oldY || force === true) {
			if (this.isVisible) {
				this._isChanged = false;
				this._isUpdating = true;

				this.node.setAttributeNS(null, 'x', x);
				this.node.setAttributeNS(null, 'y', y);
				this.node.removeAttributeNS(null, "fill-opacity");

				//this.node.setAttributeNS(null, 'font-size', this._fontSize);
				//this.node.setAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, 'align', this._horizontalAlign + " " + this._verticalAlign);

				this.oldX = x;
				this.oldY = y;

				//set rotation
				if (!this.position && !this.getReferencePoint()) {
					if (this._rotate !== undefined) {
						if (this._rotationPoint)
							this.node.setAttributeNS(null, 'transform', 'rotate(' + this._rotate + ' ' + Math.floor(this._rotationPoint.x) + ' ' + Math.floor(this._rotationPoint.y) + ')');
						else
							this.node.setAttributeNS(null, 'transform', 'rotate(' + this._rotate + ' ' + Math.floor(x) + ' ' + Math.floor(y) + ')');
					}
				} else {
					this.node.removeAttributeNS(null, 'transform');
				}

				var textLines = this._text.split("\n");
				while (textLines.last() == "")
					textLines.pop();


				if (this.node.ownerDocument) {
					// Only reset the tspans if the text 
					// has changed or has to be wrapped
					if (this.fitToElemId || this._textHasChanged) {
						this.node.textContent = ""; // Remove content
						textLines.each((function (textLine, index) {
							var tspan = this.node.ownerDocument.createElementNS(ORYX.CONFIG.NAMESPACE_SVG, 'tspan');
							tspan.textContent = textLine.trim();
							if (this.fitToElemId) {
								tspan.setAttributeNS(null, 'x', this.invisibleRenderPoint);
								tspan.setAttributeNS(null, 'y', this.invisibleRenderPoint);
							}

							/*
							 * Chrome's getBBox() method fails, if a text node contains an empty tspan element.
							 * So, we add a whitespace to such a tspan element.
							 */
							if (tspan.textContent === "") {
								tspan.textContent = " ";
							}

							//append tspan to text node
							this.node.appendChild(tspan);
						}).bind(this));
						delete this._textHasChanged;
						delete this.indices;
					}

					//Work around for Mozilla bug 293581
					if (this.isVisible && this.fitToElemId) {
						this.node.setAttributeNS(null, 'visibility', 'hidden');
					}

					if (this.fitToElemId) {
						window.setTimeout(this._checkFittingToReferencedElem.bind(this), 0);
					} else {
						window.setTimeout(this._positionText.bind(this), 0);
						//this._positionText();
					}
				}
			} else {
				this.node.textContent = "";
				//this.node.setAttributeNS(null, "fill-opacity", "0.2");
			}
		}
	},

	_checkFittingToReferencedElem: function () {
		try {
			var tspans = $A(this.node.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'tspan'));

			//only do this in firefox 3. all other browsers do not support word wrapping!!!!!
			//if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent) && new Number(RegExp.$1)>=3) {
			var newtspans = [];

			var refNode = this.node.ownerDocument.getElementById(this.fitToElemId);

			if (refNode) {

				var refbb = refNode.getBBox();

				var fontSize = this.getFontSize();

				for (var j = 0; j < tspans.length; j++) {
					var tspan = tspans[j];

					var textLength = this._getRenderedTextLength(tspan, undefined, undefined, fontSize);

					var refBoxLength = (this._rotate != 0
						&& this._rotate % 180 != 0
						&& this._rotate % 90 == 0 ?
						refbb.height : refbb.width);

					if (textLength > refBoxLength) {

						var startIndex = 0;
						var lastSeperatorIndex = 0;

						var numOfChars = this.getTrimmedTextLength(tspan.textContent);
						for (var i = 0; i < numOfChars; i++) {
							var sslength = this._getRenderedTextLength(tspan, startIndex, i - startIndex, fontSize);

							if (sslength > refBoxLength - 2) {
								var newtspan = this.node.ownerDocument.createElementNS(ORYX.CONFIG.NAMESPACE_SVG, 'tspan');
								if (lastSeperatorIndex <= startIndex) {
									lastSeperatorIndex = (i == 0) ? i : i - 1;
									newtspan.textContent = tspan.textContent.slice(startIndex, lastSeperatorIndex).trim();
									//lastSeperatorIndex = i;
								}
								else {
									newtspan.textContent = tspan.textContent.slice(startIndex, ++lastSeperatorIndex).trim();
								}

								newtspan.setAttributeNS(null, 'x', this.invisibleRenderPoint);
								newtspan.setAttributeNS(null, 'y', this.invisibleRenderPoint);

								//insert tspan to text node
								//this.node.insertBefore(newtspan, tspan);
								newtspans.push(newtspan);

								startIndex = lastSeperatorIndex;

							}
							else {
								var curChar = tspan.textContent.charAt(i);
								if (curChar == ' ' ||
									curChar == '-' ||
									curChar == "." ||
									curChar == "," ||
									curChar == ";" ||
									curChar == ":") {
									lastSeperatorIndex = i;
								}
							}
						}

						tspan.textContent = tspan.textContent.slice(startIndex).trim();
					}

					newtspans.push(tspan);
				}

				while (this.node.hasChildNodes())
					this.node.removeChild(this.node.childNodes[0]);

				while (newtspans.length > 0) {
					this.node.appendChild(newtspans.shift());
				}
			}
			//}
		} catch (e) {
			ORYX.Log.fatal("Error " + e);
		}

		window.setTimeout(this._positionText.bind(this), 0);
	},

	/**
	 * This is a work around method for Mozilla bug 293581.
	 * Before the method getComputedTextLength works, the text has to be rendered.
	 */
	_positionText: function () {
		try {
			//var tspans = this.node.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'tspan');
			var tspans = this.node.childNodes;

			var fontSize = this.getFontSize(this.node);

			var invalidTSpans = [];

			var x = this.x, y = this.y;
			if (this.position) {
				x = this.position.x;
				y = this.position.y;
			}
			x = Math.floor(x); y = Math.floor(y);

			var i = 0, indic = []; // Cache indices if the _positionText is called again, before update is called 
			var is = (this.indices || $R(0, tspans.length - 1).toArray());
			var length = is.length;
			is.each((function (index) {
				if ("undefined" == typeof index) {
					return;
				}

				var tspan = tspans[i++];

				if (tspan.textContent.trim() === "") {
					invalidTSpans.push(tspan);
				} else {
					//set vertical position
					var dy = 0;
					switch (this._verticalAlign) {
						case 'bottom':
							dy = -(length - index - 1) * (fontSize);
							break;
						case 'middle':
							dy = -(length / 2.0 - index - 1) * (fontSize);
							dy -= ORYX.CONFIG.LABEL_LINE_DISTANCE / 2;
							break;
						case 'top':
							dy = index * (fontSize);
							dy += fontSize;
							break;
					}

					tspan.setAttributeNS(null, 'dy', Math.floor(dy));

					tspan.setAttributeNS(null, 'x', x);
					tspan.setAttributeNS(null, 'y', y);
					indic.push(index);
				}

			}).bind(this));

			indic.length = tspans.length;
			this.indices = this.indices || indic;

			invalidTSpans.each(function (tspan) {
				this.node.removeChild(tspan)
			}.bind(this));

			//set horizontal alignment
			switch (this._horizontalAlign) {
				case 'left':
					this.node.setAttributeNS(null, 'text-anchor', 'start');
					break;
				case 'center':
					this.node.setAttributeNS(null, 'text-anchor', 'middle');
					break;
				case 'right':
					this.node.setAttributeNS(null, 'text-anchor', 'end');
					break;
			}

		} catch (e) {
			//console.log(e);
			this._isChanged = true;
		}


		if (this.isVisible) {
			this.node.removeAttributeNS(null, 'visibility');
		}


		// Finished
		delete this._isUpdating;

		// Raise change event
		(this.changeCallbacks || []).each(function (fn) {
			fn.apply(fn);
		})

	},

	/**
	 * Returns the text length of the text content of an SVG tspan element.
	 * For all browsers but Firefox 3 the values are estimated.
	 * @param {TSpanSVGElement} tspan
	 * @param {int} startIndex Optional, for sub strings
	 * @param {int} endIndex Optional, for sub strings
	 */
	_getRenderedTextLength: function (tspan, startIndex, endIndex, fontSize) {
		if (startIndex === undefined) {
			return tspan.getComputedTextLength();
		} else {
			return tspan.getSubStringLength(startIndex, endIndex);
		}
	},

	/**
	 * Estimates the text width for a string.
	 * Used for word wrapping in all browser but FF3.
	 * @param {Object} text
	 */
	_estimateTextWidth: function (text, fontSize) {
		var sum = 0.0;
		for (var i = 0; i < text.length; i++) {
			sum += this._estimateCharacterWidth(text.charAt(i));
		}

		return sum * (fontSize / 14.0);
	},

	/**
	 * Estimates the width of a single character for font size 14.
	 * Used for word wrapping in all browser but FF3.
	 * @param {Object} character
	 */
	_estimateCharacterWidth: function (character) {
		for (var i = 0; i < this._characterSets.length; i++) {
			if (this._characterSets[i].indexOf(character) >= 0) {
				return this._characterSetValues[i];
			}
		}
		return 9;
	},

	getReferencedElementWidth: function () {
		var refNode = this.node.ownerDocument.getElementById(this.fitToElemId);

		if (refNode) {
			var refbb = refNode.getBBox();

			if (refbb) {
				return (this._rotate != 0
					&& this._rotate % 180 != 0
					&& this._rotate % 90 == 0 ?
					refbb.height : refbb.width);
			}
		}

		return undefined;
	},

	/**
	 * If no parameter is provided, this method returns the current text.
	 * @param text {String} Optional. Replaces the old text with this one.
	 */
	text: function () {
		switch (arguments.length) {
			case 0:
				return this._text
				break;

			case 1:
				var oldText = this._text;
				if (arguments[0]) {
					this._text = arguments[0].toString();
				} else {
					this._text = "";
				}
				if (oldText !== this._text) {
					this._isChanged = true;
					this._textHasChanged = true;
				}
				break;

			default:
				//TODO error
				break;
		}
	},

	getOriginVerticalAlign: function () {
		return this._originVerticalAlign;
	},

	verticalAlign: function () {
		switch (arguments.length) {
			case 0:
				return this._verticalAlign;
			case 1:
				if (['top', 'middle', 'bottom'].member(arguments[0])) {
					var oldValue = this._verticalAlign;
					this._verticalAlign = arguments[0];
					if (this._verticalAlign !== oldValue) {
						this._isChanged = true;
					}
				}
				break;

			default:
				//TODO error
				break;
		}
	},

	getOriginHorizontalAlign: function () {
		return this._originHorizontalAlign;
	},

	horizontalAlign: function () {
		switch (arguments.length) {
			case 0:
				return this._horizontalAlign;
			case 1:
				if (['left', 'center', 'right'].member(arguments[0])) {
					var oldValue = this._horizontalAlign;
					this._horizontalAlign = arguments[0];
					if (this._horizontalAlign !== oldValue) {
						this._isChanged = true;
					}
				}
				break;

			default:
				//TODO error
				break;
		}
	},

	rotate: function () {
		switch (arguments.length) {
			case 0:
				return this._rotate;
			case 1:
				if (this._rotate != arguments[0]) {
					this._rotate = arguments[0];
					this._rotationPoint = undefined;
					this._isChanged = true;
				}
			case 2:
				if (this._rotate != arguments[0] ||
					!this._rotationPoint ||
					this._rotationPoint.x != arguments[1].x ||
					this._rotationPoint.y != arguments[1].y) {
					this._rotate = arguments[0];
					this._rotationPoint = arguments[1];
					this._isChanged = true;
				}

		}
	},

	hide: function () {
		if (this.isVisible) {
			this.isVisible = false;
			this._isChanged = true;
		}
	},

	show: function () {
		if (!this.isVisible) {
			this.isVisible = true;
			this._isChanged = true;
		}
	},

	/**
	 * iterates parent nodes till it finds a SVG font-size
	 * attribute.
	 * @param {SVGElement} node
	 */
	getInheritedFontSize: function (node) {
		if (!node || !node.getAttributeNS)
			return;

		var attr = node.getAttributeNS(null, "font-size");
		if (attr) {
			return parseFloat(attr);
		} else if (!ORYX.Editor.checkClassType(node, SVGSVGElement)) {
			return this.getInheritedFontSize(node.parentNode);
		}
	},

	getFontSize: function (node) {
		var tspans = this.node.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'tspan');

		//trying to get an inherited font-size attribute
		//NO CSS CONSIDERED!
		var fontSize = this.getInheritedFontSize(this.node);

		if (!fontSize) {
			//because this only works in firefox 3, all other browser use the default line height
			if (tspans[0] && /Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent) && new Number(RegExp.$1) >= 3) {
				fontSize = tspans[0].getExtentOfChar(0).height;
			}
			else {
				fontSize = ORYX.CONFIG.LABEL_DEFAULT_LINE_HEIGHT;
			}

			//handling of unsupported method in webkit
			if (fontSize <= 0) {
				fontSize = ORYX.CONFIG.LABEL_DEFAULT_LINE_HEIGHT;
			}
		}

		if (fontSize)
			this.node.setAttribute("oryx:fontSize", fontSize);

		return fontSize;
	},

	/**
	 * Get trimmed text length for use with
	 * getExtentOfChar and getSubStringLength.
	 * @param {String} text
	 */
	getTrimmedTextLength: function (text) {
		text = text.strip().gsub('  ', ' ');

		var oldLength;
		do {
			oldLength = text.length;
			text = text.gsub('  ', ' ');
		} while (oldLength > text.length);

		return text.length;
	},

	/**
	 * Returns the offset from
	 * edge to the label which is 
	 * positioned under the edge
	 * @return {int}
	 */
	getOffsetBottom: function () {
		return this.offsetBottom;
	},


	/**
	 * Returns the offset from
	 * edge to the label which is 
	 * positioned over the edge
	 * @return {int}
	 */
	getOffsetTop: function () {
		return this.offsetTop;
	},

	/**
	 * 
	 * @param {Object} obj
	 */
	deserialize: function (obj, shape) {
		if (obj && "undefined" != typeof obj.x && "undefined" != typeof obj.y) {
			this.setPosition({ x: obj.x, y: obj.y });

			if ("undefined" != typeof obj.distance) {
				var from = shape.dockers[obj.from];
				var to = shape.dockers[obj.to];
				if (from && to) {
					this.setReferencePoint({
						dirty: true,
						distance: obj.distance,
						intersection: { x: obj.x, y: obj.y },
						orientation: obj.orientation,
						segment: {
							from: from,
							fromIndex: obj.from,
							fromPosition: from.bounds.center(),
							to: to,
							toIndex: obj.to,
							toPosition: to.bounds.center()
						}
					})
				}
			}

			if (obj.left) this.anchorLeft = true;
			if (obj.right) this.anchorRight = true;
			if (obj.top) this.anchorTop = true;
			if (obj.bottom) this.anchorBottom = true;
			if (obj.valign) this.verticalAlign(obj.valign);
			if (obj.align) this.horizontalAlign(obj.align);

		} else if (obj && "undefined" != typeof obj.edge) {
			this.setEdgePosition(obj.edge);
		}
	},

	/**
	 * 
	 * @return {Object}
	 */
	serialize: function () {

		// On edge position
		if (this.getEdgePosition()) {
			if (this.getOriginEdgePosition() !== this.getEdgePosition()) {
				return { edge: this.getEdgePosition() };
			} else {
				return null;
			}
		}

		// On self defined position
		if (this.position) {
			var pos = { x: this.position.x, y: this.position.y };
			if (this.isAnchorLeft() && this.isAnchorLeft() !== this.isOriginAnchorLeft()) {
				pos.left = true;
			}
			if (this.isAnchorRight() && this.isAnchorRight() !== this.isOriginAnchorRight()) {
				pos.right = true;
			}
			if (this.isAnchorTop() && this.isAnchorTop() !== this.isOriginAnchorTop()) {
				pos.top = true;
			}
			if (this.isAnchorBottom() && this.isAnchorBottom() !== this.isOriginAnchorBottom()) {
				pos.bottom = true;
			}

			if (this.getOriginVerticalAlign() !== this.verticalAlign()) {
				pos.valign = this.verticalAlign();
			}
			if (this.getOriginHorizontalAlign() !== this.horizontalAlign()) {
				pos.align = this.horizontalAlign();
			}

			return pos;
		}

		// On reference point which is interesting for edges
		if (this.getReferencePoint()) {
			var ref = this.getReferencePoint();
			return {
				distance: ref.distance,
				x: ref.intersection.x,
				y: ref.intersection.y,
				from: ref.segment.fromIndex,
				to: ref.segment.toIndex,
				orientation: ref.orientation,
				valign: this.verticalAlign(),
				align: this.horizontalAlign()
			}
		}
		return null;
	},

	toString: function () { return "Label " + this.id }
});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.Math) { ORYX.Core.Math = {}; }

/**
 * Calculate the middle point between two given points
 * @param {x:double, y:double} point1
 * @param {x:double, y:double} point2
 * @return the middle point
 */
ORYX.Core.Math.midPoint = function (point1, point2) {
	return {
		x: (point1.x + point2.x) / 2.0,
		y: (point1.y + point2.y) / 2.0
	}
}

/**
 * Returns a TRUE if the point is over a line (defined by
 * point1 and point 2). In Addition a threshold can be set,
 * which defines the weight of those line.
 * 
 * @param {int} pointX - Point X
 * @param {int} pointY - Point Y
 * @param {int} lPoint1X - Line first Point X
 * @param {int} lPoint1Y - Line first Point Y
 * @param {int} lPoint2X - Line second Point X
 * @param {int} lPoint2Y - Line second Point y
 * @param {int} offset {optional} - maximal distance to line
 * @class ORYX.Core.Math.prototype
 */
ORYX.Core.Math.isPointInLine = function (pointX, pointY, lPoint1X, lPoint1Y, lPoint2X, lPoint2Y, offset) {

	offset = offset ? Math.abs(offset) : 1;

	// Check if the edge is vertical
	if (Math.abs(lPoint1X - lPoint2X) <= offset && Math.abs(pointX - lPoint1X) <= offset && pointY - Math.max(lPoint1Y, lPoint2Y) <= offset && Math.min(lPoint1Y, lPoint2Y) - pointY <= offset) {
		return true
	}

	// Check if the edge is horizontal
	if (Math.abs(lPoint1Y - lPoint2Y) <= offset && Math.abs(pointY - lPoint1Y) <= offset && pointX - Math.max(lPoint1X, lPoint2X) <= offset && Math.min(lPoint1X, lPoint2X) - pointX <= offset) {
		return true
	}

	if (pointX > Math.max(lPoint1X, lPoint2X) || pointX < Math.min(lPoint1X, lPoint2X)) {
		return false
	}

	if (pointY > Math.max(lPoint1Y, lPoint2Y) || pointY < Math.min(lPoint1Y, lPoint2Y)) {
		return false
	}

	var s = (lPoint1Y - lPoint2Y) / (lPoint1X - lPoint2X);

	return Math.abs(pointY - ((s * pointX) + lPoint1Y - s * lPoint1X)) < offset
}

/**
 * Get a boolean if the point is in the polygone
 * 
 */
ORYX.Core.Math.isPointInEllipse = function (pointX, pointY, cx, cy, rx, ry) {

	if (cx === undefined || cy === undefined || rx === undefined || ry === undefined) {
		throw "ORYX.Core.Math.isPointInEllipse needs a ellipse with these properties: x, y, radiusX, radiusY"
	}

	var tx = (pointX - cx) / rx;
	var ty = (pointY - cy) / ry;

	return tx * tx + ty * ty < 1.0;
}

/**
 * Get a boolean if the point is in the polygone
 * @param {int} pointX
 * @param {int} pointY
 * @param {[int]} Cornerpoints of the Polygone (x,y,x,y,...)
 */
ORYX.Core.Math.isPointInPolygone = function (pointX, pointY, polygone) {

	if (arguments.length < 3) {
		throw "ORYX.Core.Math.isPointInPolygone needs two arguments"
	}

	var lastIndex = polygone.length - 1;

	if (polygone[0] !== polygone[lastIndex - 1] || polygone[1] !== polygone[lastIndex]) {
		polygone.push(polygone[0]);
		polygone.push(polygone[1]);
	}

	var crossings = 0;

	var x1, y1, x2, y2, d;

	for (var i = 0; i < polygone.length - 3;) {
		x1 = polygone[i];
		y1 = polygone[++i];
		x2 = polygone[++i];
		y2 = polygone[i + 1];
		d = (pointY - y1) * (x2 - x1) - (pointX - x1) * (y2 - y1);

		if ((y1 >= pointY) != (y2 >= pointY)) {
			crossings += y2 - y1 >= 0 ? d >= 0 : d <= 0;
		}
		if (!d && Math.min(x1, x2) <= pointX && pointX <= Math.max(x1, x2)
			&& Math.min(y1, y2) <= pointY && pointY <= Math.max(y1, y2)) {
			return true;
		}
	}
	return (crossings % 2) ? true : false;
}

/**
 *	Calculates the distance between a point and a line. It is also testable, if 
 *  the distance orthogonal to the line, matches the segment of the line.
 *  
 *  @param {float} lineP1
 *  	The starting point of the line segment
 *  @param {float} lineP2
 *  	The end point of the line segment
 *  @param {Point} point
 *  	The point to calculate the distance to.
 *  @param {boolean} toSegmentOnly
 *  	Flag to signal if only the segment of the line shell be evaluated.
 */
ORYX.Core.Math.distancePointLinie = function (
	lineP1,
	lineP2,
	point,
	toSegmentOnly) {

	var intersectionPoint =
		ORYX.Core.Math.getPointOfIntersectionPointLine(lineP1,
			lineP2,
			point,
			toSegmentOnly);

	if (!intersectionPoint) {
		return null;
	}

	return ORYX.Core.Math.getDistancePointToPoint(point, intersectionPoint);
};

/**
 * Calculates the distance between two points.
 * 
 * @param {point} point1
 * @param {point} point2
 */
ORYX.Core.Math.getDistancePointToPoint = function (point1, point2) {
	return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
};

/**
 * Calculates the relative distance of a point which is between two other points.
 * 
 * @param {point} between1
 * @param {point} between2
 * @param {point} point
 */
ORYX.Core.Math.getDistanceBetweenTwoPoints = function (between1, between2, point) {
	return ORYX.Core.Math.getDistancePointToPoint(point, between1) /
		ORYX.Core.Math.getDistancePointToPoint(between1, between2);
};


/**
 * Returns true, if the point is of the left hand
 * side of the regarding the line.
 * 
 * @param {point} lineP1 Line first point
 * @param {point} lineP2 Line second point
 * @param {point} point
 */
ORYX.Core.Math.pointIsLeftOfLine = function (lineP1, lineP2, point) {

	var vec1 = ORYX.Core.Math.getVector(lineP1, lineP2);
	var vec2 = ORYX.Core.Math.getVector(lineP1, point);
	// if the cross produkt is more than 0
	return ((vec1.x * vec2.y) - (vec2.x * vec1.y)) > 0
};

/**
 * Calculates the a point which is relatively between two other points.
 * 
 * @param {point} point1
 * @param {point} point2
 * @param {number} relative Relative which is between 0 and 1
 */
ORYX.Core.Math.getPointBetweenTwoPoints = function (point1, point2, relative) {
	relative = Math.max(Math.min(relative || 0, 1), 0);

	if (relative === 0) {
		return point1;
	} else if (relative === 1) {
		return point2;
	}

	return {
		x: point1.x + ((point2.x - point1.x) * relative),
		y: point1.y + ((point2.y - point1.y) * relative)
	}
};


/**
 * Returns the vector of the both points
 *
 * @param {point} point1
 * @param {point} point2
 */
ORYX.Core.Math.getVector = function (point1, point2) {
	return {
		x: point2.x - point1.x,
		y: point2.y - point1.y
	}
}

/**
 * Returns the an identity vector of the given vector, 
 * which has the length ot one.
 *
 * @param {point} vector
 * or 
 * @param {point} point1
 * @param {point} point2
 */
ORYX.Core.Math.getIdentityVector = function (vector) {

	if (arguments.length == 2) {
		vector = ORYX.Core.Math.getVector(arguments[0], arguments[1]);
	}

	var length = Math.sqrt((vector.x * vector.x) + (vector.y * vector.y))
	return {
		x: vector.x / (length || 1),
		y: vector.y / (length || 1)
	}
}


ORYX.Core.Math.getOrthogonalIdentityVector = function (point1, point2) {
	var vec = arguments.length == 1 ? point1 : ORYX.Core.Math.getIdentityVector(point1, point2);
	return {
		x: vec.y,
		y: -vec.x
	}
}


/**
 * Returns the intersection point of a line and a point that defines a line
 * orthogonal to the given line.
 * 
 *  @param {float} lineP1
 *  	The starting point of the line segment
 *  @param {float} lineP2
 *  	The end point of the line segment
 *  @param {Point} point
 *  	The point to calculate the distance to.
 *  @param {boolean} onSegmentOnly
 *  	Flag to signal if only the segment of the line shell be evaluated.
 */
ORYX.Core.Math.getPointOfIntersectionPointLine = function (
	lineP1,
	lineP2,
	point,
	onSegmentOnly) {

	/* 
	 * [P3 - P1 - u(P2 - P1)] dot (P2 - P1) = 0
	 * u =((x3-x1)(x2-x1)+(y3-y1)(y2-y1))/(p2-p1)²
	 */
	var denominator = Math.pow(lineP2.x - lineP1.x, 2)
		+ Math.pow(lineP2.y - lineP1.y, 2);
	if (denominator == 0) {
		return undefined;
	}

	var u = ((point.x - lineP1.x) * (lineP2.x - lineP1.x)
		+ (point.y - lineP1.y) * (lineP2.y - lineP1.y))
		/ denominator;

	if (onSegmentOnly) {
		if (!(0 <= u && u <= 1)) {
			return undefined;
		}
	}

	pointOfIntersection = new Object();
	pointOfIntersection.x = lineP1.x + u * (lineP2.x - lineP1.x);
	pointOfIntersection.y = lineP1.y + u * (lineP2.y - lineP1.y);

	return pointOfIntersection;
};

/**
 * Translated the point with the given matrix.
 * @param {Point} point
 * @param {Matrix} matrix 
 * @return {Object} Includes x, y
 */
ORYX.Core.Math.getTranslatedPoint = function (point, matrix) {
	var x = matrix.a * point.x + matrix.c * point.y + matrix.e * 1;
	var y = matrix.b * point.x + matrix.d * point.y + matrix.f * 1;
	return { x: x, y: y }
}


/**
 * Returns the inverse matrix of the given SVG transformation matrix
 * @param {SVGTransformationMatrix} matrix
 * @return {Matrix}
 */
ORYX.Core.Math.getInverseMatrix = function (matrix) {

	var det = ORYX.Core.Math.getDeterminant(matrix), m = matrix;
	// +-     -+
	// | a c e |
	// | b d f |
	// | 0 0 1 |
	// +-     -+
	return {
		a: det * ((m.d * 1) - (m.f * 0)),
		b: det * ((m.f * 0) - (m.b * 1)),
		c: det * ((m.e * 0) - (m.c * 1)),
		d: det * ((m.a * 1) - (m.e * 0)),
		e: det * ((m.c * m.f) - (m.e * m.d)),
		f: det * ((m.e * m.b) - (m.a * m.f))
	}
}

/**
 * Returns the determinant of the svg transformation matrix
 * @param {SVGTranformationMatrix} matrix
 * @return {Number}
 *
 */
ORYX.Core.Math.getDeterminant = function (m) {
	// a11a22a33+a12a23a31+a13a21a32-a13a22a31-a12a21a33-a11a23a32
	return (m.a * m.d * 1) + (m.c * m.f * 0) + (m.e * m.b * 0) - (m.e * m.d * 0) - (m.c * m.b * 1) - (m.a * m.f * 0);
}

/**
 * Returns the bounding box of the given node. Translates the 
 * origin bounding box with the tranlation matrix.
 * @param {SVGElement} node
 * @return {Object} Includes x, y, width, height
 */
ORYX.Core.Math.getTranslatedBoundingBox = function (node) {
	var matrix = node.getCTM();
	var bb = node.getBBox();
	var ul = ORYX.Core.Math.getTranslatedPoint({ x: bb.x, y: bb.y }, matrix);
	var ll = ORYX.Core.Math.getTranslatedPoint({ x: bb.x, y: bb.y + bb.height }, matrix);
	var ur = ORYX.Core.Math.getTranslatedPoint({ x: bb.x + bb.width, y: bb.y }, matrix);
	var lr = ORYX.Core.Math.getTranslatedPoint({ x: bb.x + bb.width, y: bb.y + bb.height }, matrix);

	var minPoint = {
		x: Math.min(ul.x, ll.x, ur.x, lr.x),
		y: Math.min(ul.y, ll.y, ur.y, lr.y)
	}
	var maxPoint = {
		x: Math.max(ul.x, ll.x, ur.x, lr.x),
		y: Math.max(ul.y, ll.y, ur.y, lr.y)
	}
	return {
		x: minPoint.x,
		y: minPoint.y,
		width: maxPoint.x - minPoint.x,
		height: maxPoint.y - minPoint.y
	}
};


/**
 * Returns the angle of the given line, which is representated by the two points
 * @param {Point} p1
 * @param {Point} p2
 * @return {Number} 0 <= x <= 359.99999
 */
ORYX.Core.Math.getAngle = function (p1, p2) {
	if (p1.x == p2.x && p1.y == p2.y)
		return 0;

	var angle = Math.asin(Math.sqrt(Math.pow(p1.y - p2.y, 2))
		/ (Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p1.y - p2.y, 2))))
		* 180 / Math.PI;

	if (p2.x >= p1.x && p2.y <= p1.y)
		return angle;
	else if (p2.x < p1.x && p2.y <= p1.y)
		return 180 - angle;
	else if (p2.x < p1.x && p2.y > p1.y)
		return 180 + angle;
	else
		return 360 - angle;
};


/**
 * Implementation of the cohen-sutherland algorithm
 */
new function () {

	var RIGHT = 2, TOP = 8, BOTTOM = 4, LEFT = 1;

	function computeOutCode(x, y, xmin, ymin, xmax, ymax) {
		var code = 0;
		if (y > ymax)
			code |= TOP;
		else if (y < ymin)
			code |= BOTTOM;
		if (x > xmax)
			code |= RIGHT;
		else if (x < xmin)
			code |= LEFT;
		return code;
	}

	/**
	 * Returns TRUE if the rectangle is over the edge and has intersection points or includes it
	 * @param {Object} x1 Point A of the line
	 * @param {Object} y1
	 * @param {Object} x2 Point B of the line
	 * @param {Object} y2
	 * @param {Object} xmin Point A of the rectangle
	 * @param {Object} ymin
	 * @param {Object} xmax Point B of the rectangle
	 * @param {Object} ymax
	 */
	ORYX.Core.Math.isRectOverLine = function (x1, y1, x2, y2, xmin, ymin, xmax, ymax) {
		return !!ORYX.Core.Math.clipLineOnRect.apply(ORYX.Core.Math, arguments);
	}

	/**
	 * Returns the clipped line on the given rectangle. If there is 
	 * no intersection, it will return NULL.
	 *  
	 * @param {Object} x1 Point A of the line
	 * @param {Object} y1
	 * @param {Object} x2 Point B of the line
	 * @param {Object} y2
	 * @param {Object} xmin Point A of the rectangle
	 * @param {Object} ymin
	 * @param {Object} xmax Point B of the rectangle
	 * @param {Object} ymax
	 */
	ORYX.Core.Math.clipLineOnRect = function (x1, y1, x2, y2, xmin, ymin, xmax, ymax) {
		//Outcodes for P0, P1, and whatever point lies outside the clip rectangle
		var outcode0, outcode1, outcodeOut, hhh = 0;
		var accept = false, done = false;

		//compute outcodes
		outcode0 = computeOutCode(x1, y1, xmin, ymin, xmax, ymax);
		outcode1 = computeOutCode(x2, y2, xmin, ymin, xmax, ymax);

		do {
			if ((outcode0 | outcode1) == 0) {
				accept = true;
				done = true;
			} else if ((outcode0 & outcode1) > 0) {
				done = true;
			} else {
				//failed both tests, so calculate the line segment to clip
				//from an outside point to an intersection with clip edge
				var x = 0, y = 0;
				//At least one endpoint is outside the clip rectangle; pick it.
				outcodeOut = outcode0 != 0 ? outcode0 : outcode1;
				//Now find the intersection point;
				//use formulas y = y0 + slope * (x - x0), x = x0 + (1/slope)* (y - y0)
				if ((outcodeOut & TOP) > 0) {
					x = x1 + (x2 - x1) * (ymax - y1) / (y2 - y1);
					y = ymax;
				} else if ((outcodeOut & BOTTOM) > 0) {
					x = x1 + (x2 - x1) * (ymin - y1) / (y2 - y1);
					y = ymin;
				} else if ((outcodeOut & RIGHT) > 0) {
					y = y1 + (y2 - y1) * (xmax - x1) / (x2 - x1);
					x = xmax;
				} else if ((outcodeOut & LEFT) > 0) {
					y = y1 + (y2 - y1) * (xmin - x1) / (x2 - x1);
					x = xmin;
				}

				//Now we move outside point to intersection point to clip
				//and get ready for next pass.
				if (outcodeOut == outcode0) {
					x1 = x;
					y1 = y;
					outcode0 = computeOutCode(x1, y1, xmin, ymin, xmax, ymax);
				} else {
					x2 = x;
					y2 = y;
					outcode1 = computeOutCode(x2, y2, xmin, ymin, xmax, ymax);
				}
			}
			hhh++;
		} while (done != true && hhh < 5000);

		if (accept) {
			return { a: { x: x1, y: y1 }, b: { x: x2, y: y2 } };
		}
		return null;
	}
}();


/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespace
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.StencilSet) { ORYX.Core.StencilSet = {}; }

/**
 * Class Stencil
 * uses Prototpye 1.5.0
 * uses Inheritance
 * 
 * This class represents one stencil of a stencil set.
 * 
 */
ORYX.Core.StencilSet.Stencil = {

	/**
	 * Constructor
	 */
	construct: function (jsonStencil, namespace, source, stencilSet, propertyPackages, defaultPosition) {
		arguments.callee.$.construct.apply(this, arguments); // super();

		// check arguments and set defaults.
		if (!jsonStencil) throw "Stencilset seems corrupt.";
		if (!namespace) throw "Stencil does not provide namespace.";
		if (!source) throw "Stencil does not provide SVG source.";
		if (!stencilSet) throw "Fatal internal error loading stencilset.";
		//if(!propertyPackages) throw "Fatal internal error loading stencilset.";

		this._source = source;
		this._jsonStencil = jsonStencil;
		this._stencilSet = stencilSet;
		this._namespace = namespace;
		this._propertyPackages = propertyPackages;

		if (defaultPosition && !this._jsonStencil.position)
			this._jsonStencil.position = defaultPosition;

		this._view;
		this._properties = new Hash();

		// check stencil consistency and set defaults.
		/*with(this._jsonStencil) {
			
			if(!type) throw "Stencil does not provide type.";
			if((type != "edge") && (type != "node"))
				throw "Stencil type must be 'edge' or 'node'.";
			if(!id || id == "") throw "Stencil does not provide valid id.";
			if(!title || title == "")
				throw "Stencil does not provide title";
			if(!description) { description = ""; };
			if(!groups) { groups = []; }
			if(!roles) { roles = []; }

			// add id of stencil to its roles
			roles.push(id);
		}*/

		//init all JSON values
		if (!this._jsonStencil.type || !(this._jsonStencil.type === "edge" || this._jsonStencil.type === "node")) {
			throw "ORYX.Core.StencilSet.Stencil(construct): Type is not defined.";
		}
		if (!this._jsonStencil.id || this._jsonStencil.id === "") {
			throw "ORYX.Core.StencilSet.Stencil(construct): Id is not defined.";
		}
		if (!this._jsonStencil.title || this._jsonStencil.title === "") {
			throw "ORYX.Core.StencilSet.Stencil(construct): Title is not defined.";
		}

		if (!this._jsonStencil.description) { this._jsonStencil.description = ""; };
		if (!this._jsonStencil.groups) { this._jsonStencil.groups = []; }
		if (!this._jsonStencil.roles) { this._jsonStencil.roles = []; }

		//add id of stencil to its roles
		this._jsonStencil.roles.push(this._jsonStencil.id);

		//prepend namespace to each role
		this._jsonStencil.roles.each((function (role, index) {
			this._jsonStencil.roles[index] = namespace + role;
		}).bind(this));

		//delete duplicate roles
		this._jsonStencil.roles = this._jsonStencil.roles.uniq();

		//make id unique by prepending namespace of stencil set
		this._jsonStencil.id = namespace + this._jsonStencil.id;

		this.postProcessProperties();

		// init serialize callback
		if (!this._jsonStencil.serialize) {
			this._jsonStencil.serialize = {};
			//this._jsonStencil.serialize = function(shape, data) { return data;};
		}

		// init deserialize callback
		if (!this._jsonStencil.deserialize) {
			this._jsonStencil.deserialize = {};
			//this._jsonStencil.deserialize = function(shape, data) { return data;};
		}

		// init layout callback
		if (!this._jsonStencil.layout) {
			this._jsonStencil.layout = []
			//this._jsonStencil.layout = function() {return true;}
		}

		//TODO does not work correctly, if the url does not exist
		//How to guarantee that the view is loaded correctly before leaving the constructor???
		var url = source + "view/" + jsonStencil.view;
		// override content type when this is webkit.

		/*
		if(Prototype.Browser.WebKit) {
			
			var req = new XMLHttpRequest;
			req.open("GET", url, false);
			req.overrideMimeType('text/xml');
			req.send(null);
			req.onload = (function() { _loadSVGOnSuccess(req.responseXML); }).bind(this);

		// else just do it.
		} else
		*/

		if (this._jsonStencil.view.trim().match(/</)) {
			var parser = new DOMParser();
			var xml = parser.parseFromString(this._jsonStencil.view, "text/xml");

			//check if result is a SVG document
			if (ORYX.Editor.checkClassType(xml.documentElement, SVGSVGElement)) {

				this._view = xml.documentElement;

				//updating link to images
				var imageElems = this._view.getElementsByTagNameNS("http://www.w3.org/2000/svg", "image");
				$A(imageElems).each((function (imageElem) {
					var link = imageElem.getAttributeNodeNS("http://www.w3.org/1999/xlink", "href");
					if (link && link.value.indexOf("://") == -1) {
						link.textContent = this._source + "view/" + link.value;
					}
				}).bind(this));
			} else {
				throw "ORYX.Core.StencilSet.Stencil(_loadSVGOnSuccess): The response is not a SVG document."
			}
		} else {
			new Ajax.Request(
				url, {
				asynchronous: false, method: 'get',
				onSuccess: this._loadSVGOnSuccess.bind(this),
				onFailure: this._loadSVGOnFailure.bind(this)
			});
		}
	},

	postProcessProperties: function () {

		// add image path to icon
		if (this._jsonStencil.icon && this._jsonStencil.icon.indexOf("://") === -1) {
			this._jsonStencil.icon = this._source + "icons/" + this._jsonStencil.icon;
		} else {
			this._jsonStencil.icon = "";
		}

		// init property packages
		if (this._jsonStencil.propertyPackages && this._jsonStencil.propertyPackages instanceof Array) {
			this._jsonStencil.propertyPackages.each((function (ppId) {
				var pp = this._propertyPackages[ppId];

				if (pp) {
					pp.each((function (prop) {
						var oProp = new ORYX.Core.StencilSet.Property(prop, this._namespace, this);
						this._properties[oProp.prefix() + "-" + oProp.id()] = oProp;
					}).bind(this));
				}
			}).bind(this));
		}

		// init properties
		if (this._jsonStencil.properties && this._jsonStencil.properties instanceof Array) {
			this._jsonStencil.properties.each((function (prop) {
				var oProp = new ORYX.Core.StencilSet.Property(prop, this._namespace, this);
				this._properties[oProp.prefix() + "-" + oProp.id()] = oProp;
			}).bind(this));
		}


	},

	/**
	 * @param {ORYX.Core.StencilSet.Stencil} stencil
	 * @return {Boolean} True, if stencil has the same namespace and type.
	 */
	equals: function (stencil) {
		return (this.id() === stencil.id());
	},

	stencilSet: function () {
		return this._stencilSet;
	},

	type: function () {
		return this._jsonStencil.type;
	},

	namespace: function () {
		return this._namespace;
	},

	id: function () {
		return this._jsonStencil.id;
	},

	idWithoutNs: function () {
		return this.id().replace(this.namespace(), "");
	},

	title: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonStencil, "title");
	},

	description: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonStencil, "description");
	},

	groups: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonStencil, "groups");
	},

	position: function () {
		return (isNaN(this._jsonStencil.position) ? 0 : this._jsonStencil.position);
	},

	view: function () {
		return this._view.cloneNode(true) || this._view;
	},

	icon: function () {
		return this._jsonStencil.icon;
	},

	fixedAspectRatio: function () {
		return this._jsonStencil.fixedAspectRatio === true;
	},

	hasMultipleRepositoryEntries: function () {
		return (this.getRepositoryEntries().length > 0);
	},

	getRepositoryEntries: function () {
		return (this._jsonStencil.repositoryEntries) ?
			$A(this._jsonStencil.repositoryEntries) : $A([]);
	},

	properties: function () {
		return this._properties.values();
	},

	property: function (id) {
		return this._properties[id];
	},

	roles: function () {
		return this._jsonStencil.roles;
	},

	defaultAlign: function () {
		if (!this._jsonStencil.defaultAlign)
			return "east";
		return this._jsonStencil.defaultAlign;
	},

	serialize: function (shape, data) {
		return this._jsonStencil.serialize;
		//return this._jsonStencil.serialize(shape, data);
	},

	deserialize: function (shape, data) {
		return this._jsonStencil.deserialize;
		//return this._jsonStencil.deserialize(shape, data);
	},

	layout: function (shape) {
		return this._jsonStencil.layout
	},

	addProperty: function (property, namespace) {
		if (property && namespace) {
			var oProp = new ORYX.Core.StencilSet.Property(property, namespace, this);
			this._properties[oProp.prefix() + "-" + oProp.id()] = oProp;
		}
	},

	removeProperty: function (propertyId) {
		if (propertyId) {
			var oProp = this._properties.values().find(function (prop) {
				return (propertyId == prop.id());
			});
			if (oProp)
				delete this._properties[oProp.prefix() + "-" + oProp.id()];
		}
	},

	_loadSVGOnSuccess: function (result) {

		var xml = null;

		/*
		 * We want to get a dom object for the requested file. Unfortunately,
		 * safari has some issues here. this is meant as a fallback for all
		 * browsers that don't recognize the svg mimetype as XML but support
		 * data: urls on Ajax calls.
		 */

		// responseXML != undefined.
		// if(!(result.responseXML))

		// get the dom by data: url.
		// xml = _evenMoreEvilHack(result.responseText, 'text/xml');

		// else

		// get it the usual way.
		xml = result.responseXML;

		//check if result is a SVG document
		if (ORYX.Editor.checkClassType(xml.documentElement, SVGSVGElement)) {

			this._view = xml.documentElement;

			//updating link to images
			var imageElems = this._view.getElementsByTagNameNS("http://www.w3.org/2000/svg", "image");
			$A(imageElems).each((function (imageElem) {
				var link = imageElem.getAttributeNodeNS("http://www.w3.org/1999/xlink", "href");
				if (link && link.value.indexOf("://") == -1) {
					link.textContent = this._source + "view/" + link.value;
				}
			}).bind(this));
		} else {
			throw "ORYX.Core.StencilSet.Stencil(_loadSVGOnSuccess): The response is not a SVG document."
		}
	},

	_loadSVGOnFailure: function (result) {
		throw "ORYX.Core.StencilSet.Stencil(_loadSVGOnFailure): Loading SVG document failed."
	},

	toString: function () { return "Stencil " + this.title() + " (" + this.id() + ")"; }
};

ORYX.Core.StencilSet.Stencil = Clazz.extend(ORYX.Core.StencilSet.Stencil);

/**
 * Transform a string into an xml document, the Safari way, as long as
 * the nightlies are broken. Even more evil version.
 * @param {Object} contentType
 * @param {Object} str
 */
function _evenMoreEvilHack(str, contentType) {

	/*
	 * This even more evil hack was taken from
	 * http://web-graphics.com/mtarchive/001606.php#chatty004999
	 */

	if (window.ActiveXObject) {
		var d = new ActiveXObject("MSXML.DomDocument");
		d.loadXML(str);
		return d;
	} else if (window.XMLHttpRequest) {
		var req = new XMLHttpRequest;
		req.open("GET", "data:" + (contentType || "application/xml") +
			";charset=utf-8," + encodeURIComponent(str), false);
		if (req.overrideMimeType) {
			req.overrideMimeType(contentType);
		}
		req.send(null);
		return req.responseXML;
	}
}

/**
 * Transform a string into an xml document, the Safari way, as long as
 * the nightlies are broken.
 * @param {Object} result the xml document object.
 */
function _evilSafariHack(serializedXML) {

	/*
	 *  The Dave way. Taken from:
	 *  http://web-graphics.com/mtarchive/001606.php
	 *  
	 *  There is another possibility to parse XML in Safari, by implementing
	 *  the DOMParser in javascript. However, in the latest nightlies of
	 *  WebKit, DOMParser is already available, but still buggy. So, this is
	 *  the best compromise for the time being.
	 */

	var xml = serializedXML;
	var url = "data:text/xml;charset=utf-8," + encodeURIComponent(xml);
	var dom = null;

	// your standard AJAX stuff
	var req = new XMLHttpRequest();
	req.open("GET", url);
	req.onload = function () { dom = req.responseXML; }
	req.send(null);

	return dom;
}
/**
* Copyright (c) 2006
* Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
**/
/**
 * Init namespace
 */
if (!ORYX) {
	var ORYX = {};
}
if (!ORYX.Core) {
	ORYX.Core = {};
}
if (!ORYX.Core.StencilSet) {
	ORYX.Core.StencilSet = {};
}

/**
 * Class Property
 * uses Prototpye 1.5.0
 * uses Inheritance
 */
ORYX.Core.StencilSet.Property = Clazz.extend({

    /**
     * Constructor
     */
	construct: function (jsonProp, namespace, stencil) {
		arguments.callee.$.construct.apply(this, arguments);

		this._jsonProp = jsonProp || ORYX.Log.error("Parameter jsonProp is not defined.");
		this._namespace = namespace || ORYX.Log.error("Parameter namespace is not defined.");
		this._stencil = stencil || ORYX.Log.error("Parameter stencil is not defined.");

		this._items = {};
		this._complexItems = {};

		jsonProp.id = jsonProp.id || ORYX.Log.error("ORYX.Core.StencilSet.Property(construct): Id is not defined.");
		jsonProp.id = jsonProp.id.toLowerCase();

		if (!jsonProp.type) {
			ORYX.Log.info("Type is not defined for stencil '%0', id '%1'. Falling back to 'String'.", stencil, jsonProp.id);
			jsonProp.type = "string";
		}
		else {
			jsonProp.type = jsonProp.type.toLowerCase();
		}

		jsonProp.prefix = jsonProp.prefix || "oryx";
		jsonProp.title = jsonProp.title || "";
		jsonProp.value = jsonProp.value || "";
		jsonProp.description = jsonProp.description || "";
		jsonProp.readonly = jsonProp.readonly || false;
		jsonProp.optional = jsonProp.optional !== false;

		//init refToView
		if (this._jsonProp.refToView) {
			if (!(this._jsonProp.refToView instanceof Array)) {
				this._jsonProp.refToView = [this._jsonProp.refToView];
			}
		}
		else {
			this._jsonProp.refToView = [];
		}

		var globalMin = this.getMinForType(jsonProp.type);
		if (jsonProp.min === undefined || jsonProp.min === null) {
			jsonProp.min = globalMin;
		} else if (jsonProp.min < globalMin) {
			jsonProp.min = globalMin;
		}

		var globalMax = this.getMaxForType(jsonProp.type);
		if (jsonProp.max === undefined || jsonProp.max === null) {
			jsonProp.max = globalMax;
		} else if (jsonProp.max > globalMax) {
			jsonProp.min = globalMax;
		}

		if (!jsonProp.fillOpacity) {
			jsonProp.fillOpacity = false;
		}

		if ("number" != typeof jsonProp.lightness) {
			jsonProp.lightness = 1
		} else {
			jsonProp.lightness = Math.max(0, Math.min(1, jsonProp.lightness))
		}

		if (!jsonProp.strokeOpacity) {
			jsonProp.strokeOpacity = false;
		}

		if (jsonProp.length === undefined || jsonProp.length === null) {
			jsonProp.length = Number.MAX_VALUE;
		}

		if (!jsonProp.wrapLines) {
			jsonProp.wrapLines = false;
		}

		if (!jsonProp.dateFormat) {
			jsonProp.dateFormat = ORYX.I18N.PropertyWindow.dateFormat || "m/d/y";
		}

		if (!jsonProp.fill) {
			jsonProp.fill = false;
		}

		if (!jsonProp.stroke) {
			jsonProp.stroke = false;
		}

		if (!jsonProp.inverseBoolean) {
			jsonProp.inverseBoolean = false;
		}

		if (!jsonProp.directlyEditable && jsonProp.directlyEditable != false) {
			jsonProp.directlyEditable = true;
		}

		if (jsonProp.visible !== false) {
			jsonProp.visible = true;
		}

		if (jsonProp.isList !== true) {
			jsonProp.isList = false;

			if (!jsonProp.list || !(jsonProp.list instanceof Array)) {
				jsonProp.list = [];
			}
		}

		if (!jsonProp.category) {
			if (jsonProp.popular) {
				jsonProp.category = "popular";
			} else {
				jsonProp.category = "others";
			}
		}

		if (!jsonProp.alwaysAppearInMultiselect) {
			jsonProp.alwaysAppearInMultiselect = false;
		}

		if (jsonProp.type === ORYX.CONFIG.TYPE_CHOICE) {
			if (jsonProp.items && jsonProp.items instanceof Array) {
				jsonProp.items.each((function (jsonItem) {
					// why is the item's value used as the key???
					this._items[jsonItem.value.toLowerCase()] = new ORYX.Core.StencilSet.PropertyItem(jsonItem, namespace, this);
				}).bind(this));
			}
			else {
				throw "ORYX.Core.StencilSet.Property(construct): No property items defined."
			}
			// extended by Kerstin (start)
		}
		else
			if (jsonProp.type === ORYX.CONFIG.TYPE_COMPLEX || jsonProp.type == ORYX.CONFIG.TYPE_MULTIPLECOMPLEX) {
				if (jsonProp.complexItems && jsonProp.complexItems instanceof Array) {
					jsonProp.complexItems.each((function (jsonComplexItem) {
						this._complexItems[jsonComplexItem.id.toLowerCase()] = new ORYX.Core.StencilSet.ComplexPropertyItem(jsonComplexItem, namespace, this);
					}).bind(this));
				}
				else {
					throw "ORYX.Core.StencilSet.Property(construct): No complex property items defined."
				}
			}
		// extended by Kerstin (end)
	},

	getMinForType: function (type) {
		if (type.toLowerCase() == ORYX.CONFIG.TYPE_INTEGER) {
			return -Math.pow(2, 31)
		} else {
			return -Number.MAX_VALUE + 1;
		}
	},
	getMaxForType: function (type) {
		if (type.toLowerCase() == ORYX.CONFIG.TYPE_INTEGER) {
			return Math.pow(2, 31) - 1
		} else {
			return Number.MAX_VALUE;
		}
	},

    /**
     * @param {ORYX.Core.StencilSet.Property} property
     * @return {Boolean} True, if property has the same namespace and id.
     */
	equals: function (property) {
		return (this._namespace === property.namespace() &&
			this.id() === property.id()) ? true : false;
	},

	namespace: function () {
		return this._namespace;
	},

	stencil: function () {
		return this._stencil;
	},

	id: function () {
		return this._jsonProp.id;
	},

	prefix: function () {
		return this._jsonProp.prefix;
	},

	type: function () {
		return this._jsonProp.type;
	},

	inverseBoolean: function () {
		return this._jsonProp.inverseBoolean;
	},

	category: function () {
		return this._jsonProp.category;
	},

	setCategory: function (value) {
		this._jsonProp.category = value;
	},

	directlyEditable: function () {
		return this._jsonProp.directlyEditable;
	},

	visible: function () {
		return this._jsonProp.visible;
	},

	title: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonProp, "title");
	},

	value: function () {
		return this._jsonProp.value;
	},

	readonly: function () {
		return this._jsonProp.readonly;
	},

	optional: function () {
		return this._jsonProp.optional;
	},

	description: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonProp, "description");
	},

    /**
     * An optional link to a SVG element so that the property affects the
     * graphical representation of the stencil.
     */
	refToView: function () {
		return this._jsonProp.refToView;
	},

    /**
     * If type is integer or float, min is the lower bounds of value.
     */
	min: function () {
		return this._jsonProp.min;
	},

    /**
     * If type ist integer or float, max is the upper bounds of value.
     */
	max: function () {
		return this._jsonProp.max;
	},

    /**
     * If type is float, this method returns if the fill-opacity property should
     *  be set.
     *  @return {Boolean}
     */
	fillOpacity: function () {
		return this._jsonProp.fillOpacity;
	},

    /**
     * If type is float, this method returns if the stroke-opacity property should
     *  be set.
     *  @return {Boolean}
     */
	strokeOpacity: function () {
		return this._jsonProp.strokeOpacity;
	},

    /**
     * If type is string or richtext, length is the maximum length of the text.
     * TODO how long can a string be.
     */
	length: function () {
		return this._jsonProp.length ? this._jsonProp.length : Number.MAX_VALUE;
	},

	wrapLines: function () {
		return this._jsonProp.wrapLines;
	},

    /**
     * If type is date, dateFormat specifies the format of the date. The format
     * specification of the ext library is used:
     *
     * Format  Output      Description
     *	------  ----------  --------------------------------------------------------------
     *	  d      10         Day of the month, 2 digits with leading zeros
     *	  D      Wed        A textual representation of a day, three letters
     *	  j      10         Day of the month without leading zeros
     *	  l      Wednesday  A full textual representation of the day of the week
     *	  S      th         English ordinal day of month suffix, 2 chars (use with j)
     *	  w      3          Numeric representation of the day of the week
     *	  z      9          The julian date, or day of the year (0-365)
     *	  W      01         ISO-8601 2-digit week number of year, weeks starting on Monday (00-52)
     *	  F      January    A full textual representation of the month
     *	  m      01         Numeric representation of a month, with leading zeros
     *	  M      Jan        Month name abbreviation, three letters
     *	  n      1          Numeric representation of a month, without leading zeros
     *	  t      31         Number of days in the given month
     *	  L      0          Whether its a leap year (1 if it is a leap year, else 0)
     *	  Y      2007       A full numeric representation of a year, 4 digits
     *	  y      07         A two digit representation of a year
     *	  a      pm         Lowercase Ante meridiem and Post meridiem
     *	  A      PM         Uppercase Ante meridiem and Post meridiem
     *	  g      3          12-hour format of an hour without leading zeros
     *	  G      15         24-hour format of an hour without leading zeros
     *	  h      03         12-hour format of an hour with leading zeros
     *	  H      15         24-hour format of an hour with leading zeros
     *	  i      05         Minutes with leading zeros
     *	  s      01         Seconds, with leading zeros
     *	  O      -0600      Difference to Greenwich time (GMT) in hours
     *	  T      CST        Timezone setting of the machine running the code
     *	  Z      -21600     Timezone offset in seconds (negative if west of UTC, positive if east)
     *
     * Example:
     *  F j, Y, g:i a  ->  January 10, 2007, 3:05 pm
     */
	dateFormat: function () {
		return this._jsonProp.dateFormat;
	},

    /**
     * If type is color, this method returns if the fill property should
     *  be set.
     *  @return {Boolean}
     */
	fill: function () {
		return this._jsonProp.fill;
	},

	/**
	 * Lightness defines the satiation of the color
	 * 0 is the pure color
	 * 1 is white
	 * @return {Integer} lightness
	 */
	lightness: function () {
		return this._jsonProp.lightness;
	},

    /**
     * If type is color, this method returns if the stroke property should
     *  be set.
     *  @return {Boolean}
     */
	stroke: function () {
		return this._jsonProp.stroke;
	},

    /**
     * If type is choice, items is a hash map with all alternative values
     * (PropertyItem objects) with id as keys.
     */
	items: function () {
		return $H(this._items).values();
	},

	item: function (value) {
		if (value) {
			return this._items[value.toLowerCase()];
		} else {
			return null;
		}
	},

	toString: function () {
		return "Property " + this.title() + " (" + this.id() + ")";
	},

	complexItems: function () {
		return $H(this._complexItems).values();
	},

	complexItem: function (id) {
		if (id) {
			return this._complexItems[id.toLowerCase()];
		} else {
			return null;
		}

	},

	complexAttributeToView: function () {
		return this._jsonProp.complexAttributeToView || "";
	},

	isList: function () {
		return !!this._jsonProp.isList;
	},

	getListItems: function () {
		return this._jsonProp.list;
	},

	/**
	 * If type is glossary link, the 
	 * type of category can be defined where
	 * the link only can go to.
	 * @return {String} The glossary category id 
	 */
	linkableType: function () {
		return this._jsonProp.linkableType || "";
	},

	alwaysAppearInMultiselect: function () {
		return this._jsonProp.alwaysAppearInMultiselect;
	},

	popular: function () {
		return this._jsonProp.popular || false;
	},

	setPopular: function () {
		this._jsonProp.popular = true;
	}

});
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespace
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.StencilSet) { ORYX.Core.StencilSet = {}; }

/**
 * Class Stencil
 * uses Prototpye 1.5.0
 * uses Inheritance
 */
ORYX.Core.StencilSet.PropertyItem = Clazz.extend({

	/**
	 * Constructor
	 */
	construct: function (jsonItem, namespace, property) {
		arguments.callee.$.construct.apply(this, arguments);

		if (!jsonItem) {
			throw "ORYX.Core.StencilSet.PropertyItem(construct): Parameter jsonItem is not defined.";
		}
		if (!namespace) {
			throw "ORYX.Core.StencilSet.PropertyItem(construct): Parameter namespace is not defined.";
		}
		if (!property) {
			throw "ORYX.Core.StencilSet.PropertyItem(construct): Parameter property is not defined.";
		}

		this._jsonItem = jsonItem;
		this._namespace = namespace;
		this._property = property;

		//init all values
		if (!jsonItem.value) {
			throw "ORYX.Core.StencilSet.PropertyItem(construct): Value is not defined.";
		}

		if (this._jsonItem.refToView) {
			if (!(this._jsonItem.refToView instanceof Array)) {
				this._jsonItem.refToView = [this._jsonItem.refToView];
			}
		} else {
			this._jsonItem.refToView = [];
		}
	},

	/**
	 * @param {ORYX.Core.StencilSet.PropertyItem} item
	 * @return {Boolean} True, if item has the same namespace and id.
	 */
	equals: function (item) {
		return (this.property().equals(item.property()) &&
			this.value() === item.value());
	},

	namespace: function () {
		return this._namespace;
	},

	property: function () {
		return this._property;
	},

	value: function () {
		return this._jsonItem.value;
	},

	title: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonItem, "title");
	},

	refToView: function () {
		return this._jsonItem.refToView;
	},

	icon: function () {
		return (this._jsonItem.icon) ? this.property().stencil()._source + "icons/" + this._jsonItem.icon : "";
	},

	toString: function () { return "PropertyItem " + this.property() + " (" + this.value() + ")"; }
});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.StencilSet) { ORYX.Core.StencilSet = {}; }

/**
 * Class Stencil
 * uses Prototpye 1.5.0
 * uses Inheritance
 */
ORYX.Core.StencilSet.ComplexPropertyItem = Clazz.extend({

	/**
	 * Constructor
	 */
	construct: function (jsonItem, namespace, property) {
		arguments.callee.$.construct.apply(this, arguments);

		if (!jsonItem) {
			throw "ORYX.Core.StencilSet.ComplexPropertyItem(construct): Parameter jsonItem is not defined.";
		}
		if (!namespace) {
			throw "ORYX.Core.StencilSet.ComplexPropertyItem(construct): Parameter namespace is not defined.";
		}
		if (!property) {
			throw "ORYX.Core.StencilSet.ComplexPropertyItem(construct): Parameter property is not defined.";
		}

		this._jsonItem = jsonItem;
		this._namespace = namespace;
		this._property = property;
		this._items = new Hash();
		this._complexItems = new Hash();

		//init all values
		if (!jsonItem.name) {
			throw "ORYX.Core.StencilSet.ComplexPropertyItem(construct): Name is not defined.";
		}

		if (!jsonItem.type) {
			throw "ORYX.Core.StencilSet.ComplexPropertyItem(construct): Type is not defined.";
		} else {
			jsonItem.type = jsonItem.type.toLowerCase();
		}

		if (jsonItem.type === ORYX.CONFIG.TYPE_CHOICE) {
			if (jsonItem.items && jsonItem.items instanceof Array) {
				jsonItem.items.each((function (item) {
					this._items[item.value] = new ORYX.Core.StencilSet.PropertyItem(item, namespace, this);
				}).bind(this));
			} else {
				throw "ORYX.Core.StencilSet.Property(construct): No property items defined."
			}
		} else if (jsonItem.type === ORYX.CONFIG.TYPE_COMPLEX) {
			if (jsonItem.complexItems && jsonItem.complexItems instanceof Array) {
				jsonItem.complexItems.each((function (complexItem) {
					this._complexItems[complexItem.id] = new ORYX.Core.StencilSet.ComplexPropertyItem(complexItem, namespace, this);
				}).bind(this));
			} else {
				throw "ORYX.Core.StencilSet.Property(construct): No property items defined."
			}
		}
	},

	/**
	 * @param {ORYX.Core.StencilSet.PropertyItem} item
	 * @return {Boolean} True, if item has the same namespace and id.
	 */
	equals: function (item) {
		return (this.property().equals(item.property()) &&
			this.name() === item.name());
	},

	namespace: function () {
		return this._namespace;
	},

	property: function () {
		return this._property;
	},

	name: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonItem, "name");
	},

	id: function () {
		return this._jsonItem.id;
	},

	type: function () {
		return this._jsonItem.type;
	},

	optional: function () {
		return this._jsonItem.optional;
	},

	width: function () {
		return this._jsonItem.width;
	},

	value: function () {
		return this._jsonItem.value;
	},

	items: function () {
		return this._items.values();
	},

	complexItems: function () {
		return this._complexItems.values();
	},

	disable: function () {
		return this._jsonItem.disable;
	}
});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.StencilSet) { ORYX.Core.StencilSet = {}; }

/**
 * Class Rules uses Prototpye 1.5.0 uses Inheritance
 * 
 * This class implements the API to check the stencil sets' rules.
 */
ORYX.Core.StencilSet.Rules = {

	/**
	 * Constructor
	 */
	construct: function () {
		arguments.callee.$.construct.apply(this, arguments);

		this._stencilSets = [];
		this._stencils = [];
		this._containerStencils = [];

		this._cachedConnectSET = new Hash();
		this._cachedConnectSE = new Hash();
		this._cachedConnectTE = new Hash();
		this._cachedCardSE = new Hash();
		this._cachedCardTE = new Hash();
		this._cachedContainPC = new Hash();
		this._cachedMorphRS = new Hash();

		this._connectionRules = new Hash();
		this._cardinalityRules = new Hash();
		this._containmentRules = new Hash();
		this._morphingRules = new Hash();
		this._layoutRules = new Hash();
	},

	/**
	 * Call this method to initialize the rules for a stencil set and all of its
	 * active extensions.
	 * 
	 * @param {Object}
	 *            stencilSet
	 */
	initializeRules: function (stencilSet) {

		var existingSS = this._stencilSets.find(function (ss) {
			return (ss.namespace() == stencilSet.namespace());
		});
		if (existingSS) {
			// reinitialize all rules
			var stencilsets = this._stencilSets.clone();
			stencilsets = stencilsets.without(existingSS);
			stencilsets.push(stencilSet);

			this._stencilSets = [];
			this._stencils = [];
			this._containerStencils = [];

			this._cachedConnectSET = new Hash();
			this._cachedConnectSE = new Hash();
			this._cachedConnectTE = new Hash();
			this._cachedCardSE = new Hash();
			this._cachedCardTE = new Hash();
			this._cachedContainPC = new Hash();
			this._cachedMorphRS = new Hash();

			this._connectionRules = new Hash();
			this._cardinalityRules = new Hash();
			this._containmentRules = new Hash();
			this._morphingRules = new Hash();
			this._layoutRules = new Hash();

			stencilsets.each(function (ss) {
				this.initializeRules(ss);
			}.bind(this));
			return;
		}
		else {
			this._stencilSets.push(stencilSet);

			var jsonRules = new Hash(stencilSet.jsonRules());
			var namespace = stencilSet.namespace();
			var stencils = stencilSet.stencils();

			stencilSet.extensions().values().each(function (extension) {
				if (extension.rules) {
					if (extension.rules.connectionRules)
						jsonRules.connectionRules = jsonRules.connectionRules.concat(extension.rules.connectionRules);
					if (extension.rules.cardinalityRules)
						jsonRules.cardinalityRules = jsonRules.cardinalityRules.concat(extension.rules.cardinalityRules);
					if (extension.rules.containmentRules)
						jsonRules.containmentRules = jsonRules.containmentRules.concat(extension.rules.containmentRules);
					if (extension.rules.morphingRules)
						jsonRules.morphingRules = jsonRules.morphingRules.concat(extension.rules.morphingRules);
				}
				if (extension.stencils)
					stencils = stencils.concat(extension.stencils);
			});

			this._stencils = this._stencils.concat(stencilSet.stencils());

			// init connection rules
			var cr = this._connectionRules;
			if (jsonRules.connectionRules) {
				jsonRules.connectionRules.each((function (rules) {
					if (this._isRoleOfOtherNamespace(rules.role)) {
						if (!cr[rules.role]) {
							cr[rules.role] = new Hash();
						}
					}
					else {
						if (!cr[namespace + rules.role])
							cr[namespace + rules.role] = new Hash();
					}

					rules.connects.each((function (connect) {
						var toRoles = [];
						if (connect.to) {
							if (!(connect.to instanceof Array)) {
								connect.to = [connect.to];
							}
							connect.to.each((function (to) {
								if (this._isRoleOfOtherNamespace(to)) {
									toRoles.push(to);
								}
								else {
									toRoles.push(namespace + to);
								}
							}).bind(this));
						}

						var role, from;
						if (this._isRoleOfOtherNamespace(rules.role))
							role = rules.role;
						else
							role = namespace + rules.role;

						if (this._isRoleOfOtherNamespace(connect.from))
							from = connect.from;
						else
							from = namespace + connect.from;

						if (!cr[role][from])
							cr[role][from] = toRoles;
						else
							cr[role][from] = cr[role][from].concat(toRoles);

					}).bind(this));
				}).bind(this));
			}

			// init cardinality rules
			var cardr = this._cardinalityRules;
			if (jsonRules.cardinalityRules) {
				jsonRules.cardinalityRules.each((function (rules) {
					var cardrKey;
					if (this._isRoleOfOtherNamespace(rules.role)) {
						cardrKey = rules.role;
					}
					else {
						cardrKey = namespace + rules.role;
					}

					if (!cardr[cardrKey]) {
						cardr[cardrKey] = {};
						for (i in rules) {
							cardr[cardrKey][i] = rules[i];
						}
					}

					var oe = new Hash();
					if (rules.outgoingEdges) {
						rules.outgoingEdges.each((function (rule) {
							if (this._isRoleOfOtherNamespace(rule.role)) {
								oe[rule.role] = rule;
							}
							else {
								oe[namespace + rule.role] = rule;
							}
						}).bind(this));
					}
					cardr[cardrKey].outgoingEdges = oe;
					var ie = new Hash();
					if (rules.incomingEdges) {
						rules.incomingEdges.each((function (rule) {
							if (this._isRoleOfOtherNamespace(rule.role)) {
								ie[rule.role] = rule;
							}
							else {
								ie[namespace + rule.role] = rule;
							}
						}).bind(this));
					}
					cardr[cardrKey].incomingEdges = ie;
				}).bind(this));
			}

			// init containment rules
			var conr = this._containmentRules;
			if (jsonRules.containmentRules) {
				jsonRules.containmentRules.each((function (rules) {
					var conrKey;
					if (this._isRoleOfOtherNamespace(rules.role)) {
						conrKey = rules.role;
					}
					else {
						this._containerStencils.push(namespace + rules.role);
						conrKey = namespace + rules.role;
					}
					if (!conr[conrKey]) {
						conr[conrKey] = [];
					}
					(rules.contains || []).each((function (containRole) {
						if (this._isRoleOfOtherNamespace(containRole)) {
							conr[conrKey].push(containRole);
						}
						else {
							conr[conrKey].push(namespace + containRole);
						}
					}).bind(this));
				}).bind(this));
			}

			// init morphing rules
			var morphr = this._morphingRules;
			if (jsonRules.morphingRules) {
				jsonRules.morphingRules.each((function (rules) {
					var morphrKey;
					if (this._isRoleOfOtherNamespace(rules.role)) {
						morphrKey = rules.role;
					}
					else {
						morphrKey = namespace + rules.role;
					}
					if (!morphr[morphrKey]) {
						morphr[morphrKey] = [];
					}
					if (!rules.preserveBounds) {
						rules.preserveBounds = false;
					}
					rules.baseMorphs.each((function (baseMorphStencilId) {
						var morphStencil = this._getStencilById(namespace + baseMorphStencilId);
						if (morphStencil) {
							morphr[morphrKey].push(morphStencil);
						}
					}).bind(this));
				}).bind(this));
			}

			// init layouting rules
			var layoutRules = this._layoutRules;
			if (jsonRules.layoutRules) {

				var getDirections = function (o) {
					return {
						"edgeRole": o.edgeRole || undefined,
						"t": o["t"] || 1,
						"r": o["r"] || 1,
						"b": o["b"] || 1,
						"l": o["l"] || 1
					}
				}

				jsonRules.layoutRules.each(function (rules) {
					var layoutKey;
					if (this._isRoleOfOtherNamespace(rules.role)) {
						layoutKey = rules.role;
					}
					else {
						layoutKey = namespace + rules.role;
					}
					if (!layoutRules[layoutKey]) {
						layoutRules[layoutKey] = {};
					}
					if (rules["in"]) {
						layoutRules[layoutKey]["in"] = getDirections(rules["in"]);
					}
					if (rules["ins"]) {
						layoutRules[layoutKey]["ins"] = (rules["ins"] || []).map(function (e) { return getDirections(e) })
					}
					if (rules["out"]) {
						layoutRules[layoutKey]["out"] = getDirections(rules["out"]);
					}
					if (rules["outs"]) {
						layoutRules[layoutKey]["outs"] = (rules["outs"] || []).map(function (e) { return getDirections(e) })
					}
				}.bind(this));
			}
		}
	},

	_getStencilById: function (id) {
		return this._stencils.find(function (stencil) {
			return stencil.id() == id;
		});
	},

	_cacheConnect: function (args) {
		result = this._canConnect(args);

		if (args.sourceStencil && args.targetStencil) {
			var source = this._cachedConnectSET[args.sourceStencil.id()];

			if (!source) {
				source = new Hash();
				this._cachedConnectSET[args.sourceStencil.id()] = source;
			}

			var edge = source[args.edgeStencil.id()];

			if (!edge) {
				edge = new Hash();
				source[args.edgeStencil.id()] = edge;
			}

			edge[args.targetStencil.id()] = result;

		} else if (args.sourceStencil) {
			var source = this._cachedConnectSE[args.sourceStencil.id()];

			if (!source) {
				source = new Hash();
				this._cachedConnectSE[args.sourceStencil.id()] = source;
			}

			source[args.edgeStencil.id()] = result;

		} else {
			var target = this._cachedConnectTE[args.targetStencil.id()];

			if (!target) {
				target = new Hash();
				this._cachedConnectTE[args.targetStencil.id()] = target;
			}

			target[args.edgeStencil.id()] = result;
		}

		return result;
	},

	_cacheCard: function (args) {

		if (args.sourceStencil) {
			var source = this._cachedCardSE[args.sourceStencil.id()]

			if (!source) {
				source = new Hash();
				this._cachedCardSE[args.sourceStencil.id()] = source;
			}

			var max = this._getMaximumNumberOfOutgoingEdge(args);
			if (max == undefined)
				max = -1;

			source[args.edgeStencil.id()] = max;
		}

		if (args.targetStencil) {
			var target = this._cachedCardTE[args.targetStencil.id()]

			if (!target) {
				target = new Hash();
				this._cachedCardTE[args.targetStencil.id()] = target;
			}

			var max = this._getMaximumNumberOfIncomingEdge(args);
			if (max == undefined)
				max = -1;

			target[args.edgeStencil.id()] = max;
		}
	},

	_cacheContain: function (args) {

		var result = [this._canContain(args),
		this._getMaximumOccurrence(args.containingStencil, args.containedStencil)]

		if (result[1] == undefined)
			result[1] = -1;

		var children = this._cachedContainPC[args.containingStencil.id()];

		if (!children) {
			children = new Hash();
			this._cachedContainPC[args.containingStencil.id()] = children;
		}

		children[args.containedStencil.id()] = result;

		return result;
	},

	/**
	 * Returns all stencils belonging to a morph group. (calculation result is
	 * cached)
	 */
	_cacheMorph: function (role) {

		var morphs = this._cachedMorphRS[role];

		if (!morphs) {
			morphs = [];

			if (this._morphingRules.keys().include(role)) {
				morphs = this._stencils.select(function (stencil) {
					return stencil.roles().include(role);
				});
			}

			this._cachedMorphRS[role] = morphs;
		}
		return morphs;
	},

	/** Begin connection rules' methods */

	/**
	 * 
	 * @param {Object}
	 *            args sourceStencil: ORYX.Core.StencilSet.Stencil | undefined
	 *            sourceShape: ORYX.Core.Shape | undefined
	 * 
	 * At least sourceStencil or sourceShape has to be specified
	 * 
	 * @return {Array} Array of stencils of edges that can be outgoing edges of
	 *         the source.
	 */
	outgoingEdgeStencils: function (args) {
		// check arguments
		if (!args.sourceShape && !args.sourceStencil) {
			return [];
		}

		// init arguments
		if (args.sourceShape) {
			args.sourceStencil = args.sourceShape.getStencil();
		}

		var _edges = [];

		// test each edge, if it can connect to source
		this._stencils.each((function (stencil) {
			if (stencil.type() === "edge") {
				var newArgs = Object.clone(args);
				newArgs.edgeStencil = stencil;
				if (this.canConnect(newArgs)) {
					_edges.push(stencil);
				}
			}
		}).bind(this));

		return _edges;
	},

	/**
	 * 
	 * @param {Object}
	 *            args targetStencil: ORYX.Core.StencilSet.Stencil | undefined
	 *            targetShape: ORYX.Core.Shape | undefined
	 * 
	 * At least targetStencil or targetShape has to be specified
	 * 
	 * @return {Array} Array of stencils of edges that can be incoming edges of
	 *         the target.
	 */
	incomingEdgeStencils: function (args) {
		// check arguments
		if (!args.targetShape && !args.targetStencil) {
			return [];
		}

		// init arguments
		if (args.targetShape) {
			args.targetStencil = args.targetShape.getStencil();
		}

		var _edges = [];

		// test each edge, if it can connect to source
		this._stencils.each((function (stencil) {
			if (stencil.type() === "edge") {
				var newArgs = Object.clone(args);
				newArgs.edgeStencil = stencil;
				if (this.canConnect(newArgs)) {
					_edges.push(stencil);
				}
			}
		}).bind(this));

		return _edges;
	},

	/**
	 * 
	 * @param {Object}
	 *            args edgeStencil: ORYX.Core.StencilSet.Stencil | undefined
	 *            edgeShape: ORYX.Core.Edge | undefined targetStencil:
	 *            ORYX.Core.StencilSet.Stencil | undefined targetShape:
	 *            ORYX.Core.Node | undefined
	 * 
	 * At least edgeStencil or edgeShape has to be specified!!!
	 * 
	 * @return {Array} Returns an array of stencils that can be source of the
	 *         specified edge.
	 */
	sourceStencils: function (args) {
		// check arguments
		if (!args ||
			!args.edgeShape && !args.edgeStencil) {
			return [];
		}

		// init arguments
		if (args.targetShape) {
			args.targetStencil = args.targetShape.getStencil();
		}

		if (args.edgeShape) {
			args.edgeStencil = args.edgeShape.getStencil();
		}

		var _sources = [];

		// check each stencil, if it can be a source
		this._stencils.each((function (stencil) {
			var newArgs = Object.clone(args);
			newArgs.sourceStencil = stencil;
			if (this.canConnect(newArgs)) {
				_sources.push(stencil);
			}
		}).bind(this));

		return _sources;
	},

	/**
	 * 
	 * @param {Object}
	 *            args edgeStencil: ORYX.Core.StencilSet.Stencil | undefined
	 *            edgeShape: ORYX.Core.Edge | undefined sourceStencil:
	 *            ORYX.Core.StencilSet.Stencil | undefined sourceShape:
	 *            ORYX.Core.Node | undefined
	 * 
	 * At least edgeStencil or edgeShape has to be specified!!!
	 * 
	 * @return {Array} Returns an array of stencils that can be target of the
	 *         specified edge.
	 */
	targetStencils: function (args) {
		// check arguments
		if (!args ||
			!args.edgeShape && !args.edgeStencil) {
			return [];
		}

		// init arguments
		if (args.sourceShape) {
			args.sourceStencil = args.sourceShape.getStencil();
		}

		if (args.edgeShape) {
			args.edgeStencil = args.edgeShape.getStencil();
		}

		var _targets = [];

		// check stencil, if it can be a target
		this._stencils.each((function (stencil) {
			var newArgs = Object.clone(args);
			newArgs.targetStencil = stencil;
			if (this.canConnect(newArgs)) {
				_targets.push(stencil);
			}
		}).bind(this));

		return _targets;
	},

	/**
	 * 
	 * @param {Object}
	 *            args edgeStencil: ORYX.Core.StencilSet.Stencil edgeShape:
	 *            ORYX.Core.Edge |undefined sourceStencil:
	 *            ORYX.Core.StencilSet.Stencil | undefined sourceShape:
	 *            ORYX.Core.Node |undefined targetStencil:
	 *            ORYX.Core.StencilSet.Stencil | undefined targetShape:
	 *            ORYX.Core.Node |undefined
	 * 
	 * At least source or target has to be specified!!!
	 * 
	 * @return {Boolean} Returns, if the edge can connect source and target.
	 */
	canConnect: function (args) {
		// check arguments
		if (!args ||
			(!args.sourceShape && !args.sourceStencil &&
				!args.targetShape && !args.targetStencil) ||
			!args.edgeShape && !args.edgeStencil) {
			return false;
		}

		// init arguments
		if (args.sourceShape) {
			args.sourceStencil = args.sourceShape.getStencil();
		}
		if (args.targetShape) {
			args.targetStencil = args.targetShape.getStencil();
		}
		if (args.edgeShape) {
			args.edgeStencil = args.edgeShape.getStencil();
		}

		var result;

		if (args.sourceStencil && args.targetStencil) {
			var source = this._cachedConnectSET[args.sourceStencil.id()];

			if (!source)
				result = this._cacheConnect(args);
			else {
				var edge = source[args.edgeStencil.id()];

				if (!edge)
					result = this._cacheConnect(args);
				else {
					var target = edge[args.targetStencil.id()];

					if (target == undefined)
						result = this._cacheConnect(args);
					else
						result = target;
				}
			}
		} else if (args.sourceStencil) {
			var source = this._cachedConnectSE[args.sourceStencil.id()];

			if (!source)
				result = this._cacheConnect(args);
			else {
				var edge = source[args.edgeStencil.id()];

				if (edge == undefined)
					result = this._cacheConnect(args);
				else
					result = edge;
			}
		} else { // args.targetStencil
			var target = this._cachedConnectTE[args.targetStencil.id()];

			if (!target)
				result = this._cacheConnect(args);
			else {
				var edge = target[args.edgeStencil.id()];

				if (edge == undefined)
					result = this._cacheConnect(args);
				else
					result = edge;
			}
		}

		// check cardinality
		if (result) {
			if (args.sourceShape) {
				var source = this._cachedCardSE[args.sourceStencil.id()];

				if (!source) {
					this._cacheCard(args);
					source = this._cachedCardSE[args.sourceStencil.id()];
				}

				var max = source[args.edgeStencil.id()];

				if (max == undefined) {
					this._cacheCard(args);
				}

				max = source[args.edgeStencil.id()];

				if (max != -1) {
					result = args.sourceShape.getOutgoingShapes().all(function (cs) {
						if ((cs.getStencil().id() === args.edgeStencil.id()) &&
							((args.edgeShape) ? cs !== args.edgeShape : true)) {
							max--;
							return (max > 0) ? true : false;
						} else {
							return true;
						}
					});
				}
			}

			if (args.targetShape) {
				var target = this._cachedCardTE[args.targetStencil.id()];

				if (!target) {
					this._cacheCard(args);
					target = this._cachedCardTE[args.targetStencil.id()];
				}

				var max = target[args.edgeStencil.id()];

				if (max == undefined) {
					this._cacheCard(args);
				}

				max = target[args.edgeStencil.id()];

				if (max != -1) {
					result = args.targetShape.getIncomingShapes().all(function (cs) {
						if ((cs.getStencil().id() === args.edgeStencil.id()) &&
							((args.edgeShape) ? cs !== args.edgeShape : true)) {
							max--;
							return (max > 0) ? true : false;
						}
						else {
							return true;
						}
					});
				}
			}
		}

		return result;
	},

	/**
	 * 
	 * @param {Object}
	 *            args edgeStencil: ORYX.Core.StencilSet.Stencil edgeShape:
	 *            ORYX.Core.Edge |undefined sourceStencil:
	 *            ORYX.Core.StencilSet.Stencil | undefined sourceShape:
	 *            ORYX.Core.Node |undefined targetStencil:
	 *            ORYX.Core.StencilSet.Stencil | undefined targetShape:
	 *            ORYX.Core.Node |undefined
	 * 
	 * At least source or target has to be specified!!!
	 * 
	 * @return {Boolean} Returns, if the edge can connect source and target.
	 */
	_canConnect: function (args) {
		// check arguments
		if (!args ||
			(!args.sourceShape && !args.sourceStencil &&
				!args.targetShape && !args.targetStencil) ||
			!args.edgeShape && !args.edgeStencil) {
			return false;
		}

		// init arguments
		if (args.sourceShape) {
			args.sourceStencil = args.sourceShape.getStencil();
		}
		if (args.targetShape) {
			args.targetStencil = args.targetShape.getStencil();
		}
		if (args.edgeShape) {
			args.edgeStencil = args.edgeShape.getStencil();
		}

		// 1. check connection rules
		var resultCR;

		// get all connection rules for this edge
		var edgeRules = this._getConnectionRulesOfEdgeStencil(args.edgeStencil);

		// check connection rules, if the source can be connected to the target
		// with the specified edge.
		if (edgeRules.keys().length === 0) {
			resultCR = false;
		} else {
			if (args.sourceStencil) {
				resultCR = args.sourceStencil.roles().any(function (sourceRole) {
					var targetRoles = edgeRules[sourceRole];

					if (!targetRoles) { return false; }

					if (args.targetStencil) {
						return (targetRoles.any(function (targetRole) {
							return args.targetStencil.roles().member(targetRole);
						}));
					} else {
						return true;
					}
				});
			} else { // !args.sourceStencil -> there is args.targetStencil
				resultCR = edgeRules.values().any(function (targetRoles) {
					return args.targetStencil.roles().any(function (targetRole) {
						return targetRoles.member(targetRole);
					});
				});
			}
		}

		return resultCR;
	},

	/** End connection rules' methods */


	/** Begin containment rules' methods */

	isContainer: function (shape) {
		return this._containerStencils.member(shape.getStencil().id());
	},

	/**
	 * 
	 * @param {Object}
	 *            args containingStencil: ORYX.Core.StencilSet.Stencil
	 *            containingShape: ORYX.Core.AbstractShape containedStencil:
	 *            ORYX.Core.StencilSet.Stencil containedShape: ORYX.Core.Shape
	 */
	canContain: function (args) {
		if (!args ||
			!args.containingStencil && !args.containingShape ||
			!args.containedStencil && !args.containedShape) {
			return false;
		}

		// init arguments
		if (args.containedShape) {
			args.containedStencil = args.containedShape.getStencil();
		}

		if (args.containingShape) {
			args.containingStencil = args.containingShape.getStencil();
		}

		//if(args.containingStencil.type() == 'edge' || args.containedStencil.type() == 'edge')
		//	return false;
		if (args.containedStencil.type() == 'edge')
			return false;

		var childValues;

		var parent = this._cachedContainPC[args.containingStencil.id()];

		if (!parent)
			childValues = this._cacheContain(args);
		else {
			childValues = parent[args.containedStencil.id()];

			if (!childValues)
				childValues = this._cacheContain(args);
		}

		if (!childValues[0])
			return false;
		else if (childValues[1] == -1)
			return true;
		else {
			if (args.containingShape) {
				var max = childValues[1];
				return args.containingShape.getChildShapes(false).all(function (as) {
					if (as.getStencil().id() === args.containedStencil.id()) {
						max--;
						return (max > 0) ? true : false;
					} else {
						return true;
					}
				});
			} else {
				return true;
			}
		}
	},

	/**
	 * 
	 * @param {Object}
	 *            args containingStencil: ORYX.Core.StencilSet.Stencil
	 *            containingShape: ORYX.Core.AbstractShape containedStencil:
	 *            ORYX.Core.StencilSet.Stencil containedShape: ORYX.Core.Shape
	 */
	_canContain: function (args) {
		if (!args ||
			!args.containingStencil && !args.containingShape ||
			!args.containedStencil && !args.containedShape) {
			return false;
		}

		// init arguments
		if (args.containedShape) {
			args.containedStencil = args.containedShape.getStencil();
		}

		if (args.containingShape) {
			args.containingStencil = args.containingShape.getStencil();
		}



		var result;

		// check containment rules
		result = args.containingStencil.roles().any((function (role) {
			var roles = this._containmentRules[role];
			if (roles) {
				return roles.any(function (role) {
					return args.containedStencil.roles().member(role);
				});
			} else {
				return false;
			}
		}).bind(this));

		return result;
	},

	/** End containment rules' methods */


	/** Begin morphing rules' methods */

	/**
	 * 
	 * @param {Object}
	 *           args 
	 *            stencil: ORYX.Core.StencilSet.Stencil | undefined 
	 *            shape: ORYX.Core.Shape | undefined
	 * 
	 * At least stencil or shape has to be specified
	 * 
	 * @return {Array} Array of stencils that the passed stencil/shape can be
	 *         transformed to (including the current stencil itself)
	 */
	morphStencils: function (args) {
		// check arguments
		if (!args.stencil && !args.shape) {
			return [];
		}

		// init arguments
		if (args.shape) {
			args.stencil = args.shape.getStencil();
		}

		var _morphStencils = [];
		args.stencil.roles().each(function (role) {
			this._cacheMorph(role).each(function (stencil) {
				_morphStencils.push(stencil);
			})
		}.bind(this));


		var baseMorphs = this.baseMorphs();
		// BaseMorphs should be in the front of the array
		_morphStencils = _morphStencils.uniq().sort(function (a, b) { return baseMorphs.include(a) && !baseMorphs.include(b) ? -1 : (baseMorphs.include(b) && !baseMorphs.include(a) ? 1 : 0) })
		return _morphStencils;
	},

	/**
	 * @return {Array} An array of all base morph stencils
	 */
	baseMorphs: function () {
		var _baseMorphs = [];
		this._morphingRules.each(function (pair) {
			pair.value.each(function (baseMorph) {
				_baseMorphs.push(baseMorph);
			});
		});
		return _baseMorphs;
	},

	/**
	 * Returns true if there are morphing rules defines
	 * @return {boolean} 
	 */
	containsMorphingRules: function () {
		return this._stencilSets.any(function (ss) { return !!ss.jsonRules().morphingRules });
	},

	/**
	 * 
	 * @param {Object}
	 *            args 
	 *            sourceStencil:
	 *            ORYX.Core.StencilSet.Stencil | undefined 
	 *            sourceShape:
	 *            ORYX.Core.Node |undefined 
	 *            targetStencil:
	 *            ORYX.Core.StencilSet.Stencil | undefined 
	 *            targetShape:
	 *            ORYX.Core.Node |undefined
	 * 
	 * 
	 * @return {Stencil} Returns, the stencil for the connecting edge 
	 * or null if connection is not possible
	 */
	connectMorph: function (args) {
		// check arguments
		if (!args ||
			(!args.sourceShape && !args.sourceStencil &&
				!args.targetShape && !args.targetStencil)) {
			return false;
		}

		// init arguments
		if (args.sourceShape) {
			args.sourceStencil = args.sourceShape.getStencil();
		}
		if (args.targetShape) {
			args.targetStencil = args.targetShape.getStencil();
		}

		var incoming = this.incomingEdgeStencils(args);
		var outgoing = this.outgoingEdgeStencils(args);

		var edgeStencils = incoming.select(function (e) { return outgoing.member(e); }); // intersection of sets
		var baseEdgeStencils = this.baseMorphs().select(function (e) { return edgeStencils.member(e); }); // again: intersection of sets

		if (baseEdgeStencils.size() > 0)
			return baseEdgeStencils[0]; // return any of the possible base morphs
		else if (edgeStencils.size() > 0)
			return edgeStencils[0];	// return any of the possible stencils

		return null; //connection not possible
	},

	/**
	 * Return true if the stencil should be located in the shape menu
	 * @param {ORYX.Core.StencilSet.Stencil} morph
	 * @return {Boolean} Returns true if the morphs in the morph group of the
	 * specified morph shall be displayed in the shape menu
	 */
	showInShapeMenu: function (stencil) {
		return this._stencilSets.any(function (ss) {
			return ss.jsonRules().morphingRules
				.any(function (r) {
					return stencil.roles().include(ss.namespace() + r.role)
						&& r.showInShapeMenu !== false;
				})
		});
	},

	preserveBounds: function (stencil) {
		return this._stencilSets.any(function (ss) {
			return ss.jsonRules().morphingRules.any(function (r) {


				return stencil.roles().include(ss.namespace() + r.role)
					&& r.preserveBounds;
			})
		})
	},

	/** End morphing rules' methods */


	/** Begin layouting rules' methods */

	/**
	 * Returns a set on "in" and "out" layouting rules for a given shape
	 * @param {Object} shape
	 * @param {Object} edgeShape (Optional)
	 * @return {Object} "in" and "out" with a default value of {"t":1, "r":1, "b":1, "r":1} if not specified in the json
	 */
	getLayoutingRules: function (shape, edgeShape) {

		if (!shape || !(shape instanceof ORYX.Core.Shape)) { return }

		var layout = { "in": {}, "out": {} };

		var parseValues = function (o, v) {
			if (o && o[v]) {
				["t", "r", "b", "l"].each(function (d) {
					layout[v][d] = Math.max(o[v][d], layout[v][d] || 0);
				});
			}
			if (o && o[v + "s"] instanceof Array) {
				["t", "r", "b", "l"].each(function (d) {
					var defaultRule = o[v + "s"].find(function (e) { return !e.edgeRole });
					var edgeRule;
					if (edgeShape instanceof ORYX.Core.Edge) {
						edgeRule = o[v + "s"].find(function (e) { return this._hasRole(edgeShape, e.edgeRole) }.bind(this));
					}
					layout[v][d] = Math.max(edgeRule ? edgeRule[d] : defaultRule[d], layout[v][d] || 0);
				}.bind(this));
			}
		}.bind(this)

		// For each role
		shape.getStencil().roles().each(function (role) {
			// check if there are layout information
			if (this._layoutRules[role]) {
				// if so, parse those information to the 'layout' variable
				parseValues(this._layoutRules[role], "in");
				parseValues(this._layoutRules[role], "out");
			}
		}.bind(this));

		// Make sure, that every attribute has an value,
		// otherwise set 1
		["in", "out"].each(function (v) {
			["t", "r", "b", "l"].each(function (d) {
				layout[v][d] = layout[v][d] !== undefined ? layout[v][d] : 1;
			});
		})

		return layout;
	},

	/** End layouting rules' methods */

	/** Helper methods */

	/**
	 * Checks wether a shape contains the given role or the role is equal the stencil id 
	 * @param {ORYX.Core.Shape} shape
	 * @param {String} role
	 */
	_hasRole: function (shape, role) {
		if (!(shape instanceof ORYX.Core.Shape) || !role) { return }
		var isRole = shape.getStencil().roles().any(function (r) { return r == role });

		return isRole || shape.getStencil().id() == (shape.getStencil().namespace() + role);
	},

	/**
	 * 
	 * @param {String}
	 *            role
	 * 
	 * @return {Array} Returns an array of stencils that can act as role.
	 */
	_stencilsWithRole: function (role) {
		return this._stencils.findAll(function (stencil) {
			return (stencil.roles().member(role)) ? true : false;
		});
	},

	/**
	 * 
	 * @param {String}
	 *            role
	 * 
	 * @return {Array} Returns an array of stencils that can act as role and
	 *         have the type 'edge'.
	 */
	_edgesWithRole: function (role) {
		return this._stencils.findAll(function (stencil) {
			return (stencil.roles().member(role) && stencil.type() === "edge") ? true : false;
		});
	},

	/**
	 * 
	 * @param {String}
	 *            role
	 * 
	 * @return {Array} Returns an array of stencils that can act as role and
	 *         have the type 'node'.
	 */
	_nodesWithRole: function (role) {
		return this._stencils.findAll(function (stencil) {
			return (stencil.roles().member(role) && stencil.type() === "node") ? true : false;
		});
	},

	/**
	 * 
	 * @param {ORYX.Core.StencilSet.Stencil}
	 *            parent
	 * @param {ORYX.Core.StencilSet.Stencil}
	 *            child
	 * 
	 * @returns {Boolean} Returns the maximum occurrence of shapes of the
	 *          stencil's type inside the parent.
	 */
	_getMaximumOccurrence: function (parent, child) {
		var max;
		child.roles().each((function (role) {
			var cardRule = this._cardinalityRules[role];
			if (cardRule && cardRule.maximumOccurrence) {
				if (max) {
					max = Math.min(max, cardRule.maximumOccurrence);
				} else {
					max = cardRule.maximumOccurrence;
				}
			}
		}).bind(this));

		return max;
	},


	/**
	 * 
	 * @param {Object}
	 *            args sourceStencil: ORYX.Core.Node edgeStencil:
	 *            ORYX.Core.StencilSet.Stencil
	 * 
	 * @return {Boolean} Returns, the maximum number of outgoing edges of the
	 *         type specified by edgeStencil of the sourceShape.
	 */
	_getMaximumNumberOfOutgoingEdge: function (args) {
		if (!args ||
			!args.sourceStencil ||
			!args.edgeStencil) {
			return false;
		}

		var max;
		args.sourceStencil.roles().each((function (role) {
			var cardRule = this._cardinalityRules[role];

			if (cardRule && cardRule.outgoingEdges) {
				args.edgeStencil.roles().each(function (edgeRole) {
					var oe = cardRule.outgoingEdges[edgeRole];

					if (oe && oe.maximum) {
						if (max) {
							max = Math.min(max, oe.maximum);
						} else {
							max = oe.maximum;
						}
					}
				});
			}
		}).bind(this));

		return max;
	},

	/**
	 * 
	 * @param {Object}
	 *            args targetStencil: ORYX.Core.StencilSet.Stencil edgeStencil:
	 *            ORYX.Core.StencilSet.Stencil
	 * 
	 * @return {Boolean} Returns the maximum number of incoming edges of the
	 *         type specified by edgeStencil of the targetShape.
	 */
	_getMaximumNumberOfIncomingEdge: function (args) {
		if (!args ||
			!args.targetStencil ||
			!args.edgeStencil) {
			return false;
		}

		var max;
		args.targetStencil.roles().each((function (role) {
			var cardRule = this._cardinalityRules[role];
			if (cardRule && cardRule.incomingEdges) {
				args.edgeStencil.roles().each(function (edgeRole) {
					var ie = cardRule.incomingEdges[edgeRole];
					if (ie && ie.maximum) {
						if (max) {
							max = Math.min(max, ie.maximum);
						} else {
							max = ie.maximum;
						}
					}
				});
			}
		}).bind(this));

		return max;
	},

	/**
	 * 
	 * @param {ORYX.Core.StencilSet.Stencil}
	 *            edgeStencil
	 * 
	 * @return {Hash} Returns a hash map of all connection rules for
	 *         edgeStencil.
	 */
	_getConnectionRulesOfEdgeStencil: function (edgeStencil) {
		var edgeRules = new Hash();
		edgeStencil.roles().each((function (role) {
			if (this._connectionRules[role]) {
				this._connectionRules[role].each(function (cr) {
					if (edgeRules[cr.key]) {
						edgeRules[cr.key] = edgeRules[cr.key].concat(cr.value);
					} else {
						edgeRules[cr.key] = cr.value;
					}
				});
			}
		}).bind(this));

		return edgeRules;
	},

	_isRoleOfOtherNamespace: function (role) {
		return (role.indexOf("#") > 0);
	},

	toString: function () { return "Rules"; }
}
ORYX.Core.StencilSet.Rules = Clazz.extend(ORYX.Core.StencilSet.Rules);
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
/**
 * Init namespace
 */
if (!ORYX) {
	var ORYX = {};
}
if (!ORYX.Core) {
	ORYX.Core = {};
}
if (!ORYX.Core.StencilSet) {
	ORYX.Core.StencilSet = {};
}

/**
 * This class represents a stencil set. It offers methods for accessing
 *  the attributes of the stencil set description JSON file and the stencil set's
 *  stencils.
 *  加载 解析stencilset.json 文件
 */
ORYX.Core.StencilSet.StencilSet = Clazz.extend({

    /**
     * Constructor
     * @param source {URL} A reference to the stencil set specification.
     *
     */
	construct: function (source) {
		arguments.callee.$.construct.apply(this, arguments);

		if (!source) {
			throw "ORYX.Core.StencilSet.StencilSet(construct): Parameter 'source' is not defined.";
		}

		if (source.endsWith("/")) {
			source = source.substr(0, source.length - 1);
		}

		this._extensions = new Hash();

		this._source = source;
		this._baseUrl = source.substring(0, source.lastIndexOf("/") + 1);

		this._jsonObject = {};

		this._stencils = new Hash();
		this._availableStencils = new Hash();

		if (ORYX.CONFIG.BACKEND_SWITCH) {
			this._baseUrl = ORYX.CONFIG.ROOT_PATH + "/stencilsets/bpmn2.0/";
			this._source = "../stencilsets/bpmn2.0/bpmn2.0.json";
			new Ajax.Request(ORYX.CONFIG.ROOT_PATH + "/stencilset.json", {
				asynchronous: false,
				method: 'get',
				onSuccess: this._init.bind(this),
				onFailure: this._cancelInit.bind(this)
			});

		} else {
			new Ajax.Request(source, {
				asynchronous: false,
				method: 'get',
				onSuccess: this._init.bind(this),
				onFailure: this._cancelInit.bind(this)
			});
		}

		if (this.errornous)
			throw "Loading stencil set " + source + " failed.";
	},

    /**
     * Finds a root stencil in this stencil set. There may be many of these. If
     * there are, the first one found will be used. In Firefox, this is the
     * topmost definition in the stencil set description file.
     */
	findRootStencilName: function () {

		// find any stencil that may be root.
		var rootStencil = this._stencils.values().find(function (stencil) {
			return stencil._jsonStencil.mayBeRoot
		});

		// if there is none, just guess the first.
		if (!rootStencil) {
			ORYX.Log.warn("Did not find any stencil that may be root. Taking a guess.");
			rootStencil = this._stencils.values()[0];
		}

		// return its id.
		return rootStencil.id();
	},

    /**
     * @param {ORYX.Core.StencilSet.StencilSet} stencilSet
     * @return {Boolean} True, if stencil set has the same namespace.
     */
	equals: function (stencilSet) {
		return (this.namespace() === stencilSet.namespace());
	},

	/**
	 * 
	 * @param {Oryx.Core.StencilSet.Stencil} rootStencil If rootStencil is defined, it only returns stencils
	 * 			that could be (in)direct child of that stencil.
	 */
	stencils: function (rootStencil, rules, sortByGroup) {
		if (rootStencil && rules) {
			var stencils = this._availableStencils.values();
			var containers = [rootStencil];
			var checkedContainers = [];

			var result = [];

			while (containers.size() > 0) {
				var container = containers.pop();
				checkedContainers.push(container);
				var children = stencils.findAll(function (stencil) {
					var args = {
						containingStencil: container,
						containedStencil: stencil
					};
					return rules.canContain(args);
				});
				for (var i = 0; i < children.size(); i++) {
					if (!checkedContainers.member(children[i])) {
						containers.push(children[i]);
					}
				}
				result = result.concat(children).uniq();
			}

			// Sort the result to the origin order
			result = result.sortBy(function (stencil) {
				return stencils.indexOf(stencil);
			});


			if (sortByGroup) {
				result = result.sortBy(function (stencil) {
					return stencil.groups().first();
				});
			}

			var edges = stencils.findAll(function (stencil) {
				return stencil.type() == "edge";
			});
			result = result.concat(edges);

			return result;

		} else {
			if (sortByGroup) {
				return this._availableStencils.values().sortBy(function (stencil) {
					return stencil.groups().first();
				});
			} else {
				return this._availableStencils.values();
			}
		}
	},

	nodes: function () {
		return this._availableStencils.values().findAll(function (stencil) {
			return (stencil.type() === 'node')
		});
	},

	edges: function () {
		return this._availableStencils.values().findAll(function (stencil) {
			return (stencil.type() === 'edge')
		});
	},

	stencil: function (id) {
		return this._stencils[id];
	},

	title: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonObject, "title");
	},

	description: function () {
		return ORYX.Core.StencilSet.getTranslation(this._jsonObject, "description");
	},

	namespace: function () {
		return this._jsonObject ? this._jsonObject.namespace : null;
	},

	jsonRules: function () {
		return this._jsonObject ? this._jsonObject.rules : null;
	},

	source: function () {
		return this._source;
	},

	extensions: function () {
		return this._extensions;
	},

	addExtension: function (url) {

		new Ajax.Request(url, {
			method: 'GET',
			asynchronous: false,
			onSuccess: (function (transport) {
				this.addExtensionDirectly(transport.responseText);
			}).bind(this),
			onFailure: (function (transport) {
				ORYX.Log.debug("Loading stencil set extension file failed. The request returned an error." + transport);
			}).bind(this),
			onException: (function (transport) {
				ORYX.Log.debug("Loading stencil set extension file failed. The request returned an error." + transport);
			}).bind(this)

		});
	},

	addExtensionDirectly: function (str) {

		try {
			eval("var jsonExtension = " + str);

			if (!(jsonExtension["extends"].endsWith("#")))
				jsonExtension["extends"] += "#";

			if (jsonExtension["extends"] == this.namespace()) {
				this._extensions[jsonExtension.namespace] = jsonExtension;

				var defaultPosition = this._stencils.keys().size();
				//load new stencils
				if (jsonExtension.stencils) {
					$A(jsonExtension.stencils).each(function (stencil) {
						defaultPosition++;
						var oStencil = new ORYX.Core.StencilSet.Stencil(stencil, this.namespace(), this._baseUrl, this, undefined, defaultPosition);
						this._stencils[oStencil.id()] = oStencil;
						this._availableStencils[oStencil.id()] = oStencil;
					}.bind(this));
				}

				//load additional properties
				if (jsonExtension.properties) {
					var stencils = this._stencils.values();

					stencils.each(function (stencil) {
						var roles = stencil.roles();

						jsonExtension.properties.each(function (prop) {
							prop.roles.any(function (role) {
								role = jsonExtension["extends"] + role;
								if (roles.member(role)) {
									prop.properties.each(function (property) {
										stencil.addProperty(property, jsonExtension.namespace);
									});

									return true;
								}
								else
									return false;
							})
						})
					}.bind(this));
				}

				//remove stencil properties
				if (jsonExtension.removeproperties) {
					jsonExtension.removeproperties.each(function (remprop) {
						var stencil = this.stencil(jsonExtension["extends"] + remprop.stencil);
						if (stencil) {
							remprop.properties.each(function (propId) {
								stencil.removeProperty(propId);
							});
						}
					}.bind(this));
				}

				//remove stencils
				if (jsonExtension.removestencils) {
					$A(jsonExtension.removestencils).each(function (remstencil) {
						delete this._availableStencils[jsonExtension["extends"] + remstencil];
					}.bind(this));
				}
			}
		} catch (e) {
			ORYX.Log.debug("StencilSet.addExtension: Something went wrong when initialising the stencil set extension. " + e);
		}
	},

	removeExtension: function (namespace) {
		var jsonExtension = this._extensions[namespace];
		if (jsonExtension) {

			//unload extension's stencils
			if (jsonExtension.stencils) {
				$A(jsonExtension.stencils).each(function (stencil) {
					var oStencil = new ORYX.Core.StencilSet.Stencil(stencil, this.namespace(), this._baseUrl, this);
					delete this._stencils[oStencil.id()]; // maybe not ??
					delete this._availableStencils[oStencil.id()];
				}.bind(this));
			}

			//unload extension's properties
			if (jsonExtension.properties) {
				var stencils = this._stencils.values();

				stencils.each(function (stencil) {
					var roles = stencil.roles();

					jsonExtension.properties.each(function (prop) {
						prop.roles.any(function (role) {
							role = jsonExtension["extends"] + role;
							if (roles.member(role)) {
								prop.properties.each(function (property) {
									stencil.removeProperty(property.id);
								});

								return true;
							}
							else
								return false;
						})
					})
				}.bind(this));
			}

			//restore removed stencil properties
			if (jsonExtension.removeproperties) {
				jsonExtension.removeproperties.each(function (remprop) {
					var stencil = this.stencil(jsonExtension["extends"] + remprop.stencil);
					if (stencil) {
						var stencilJson = $A(this._jsonObject.stencils).find(function (s) { return s.id == stencil.id() });
						remprop.properties.each(function (propId) {
							var propertyJson = $A(stencilJson.properties).find(function (p) { return p.id == propId });
							stencil.addProperty(propertyJson, this.namespace());
						}.bind(this));
					}
				}.bind(this));
			}

			//restore removed stencils
			if (jsonExtension.removestencils) {
				$A(jsonExtension.removestencils).each(function (remstencil) {
					var sId = jsonExtension["extends"] + remstencil;
					this._availableStencils[sId] = this._stencils[sId];
				}.bind(this));
			}
		}
		delete this._extensions[namespace];
	},

	__handleStencilset: function (response) {

		try {
			// using eval instead of prototype's parsing,
			// since there are functions in this JSON.
			eval("this._jsonObject =" + response.responseText);
		}
		catch (e) {
			throw "Stenciset corrupt: " + e;
		}

		// assert it was parsed.
		if (!this._jsonObject) {
			throw "Error evaluating stencilset. It may be corrupt.";
		}

		with (this._jsonObject) {

			// assert there is a namespace.
			if (!namespace || namespace === "")
				throw "Namespace definition missing in stencilset.";

			if (!(stencils instanceof Array))
				throw "Stencilset corrupt.";

			// assert namespace ends with '#'.
			if (!namespace.endsWith("#"))
				namespace = namespace + "#";

			// assert title and description are strings.
			if (!title)
				title = "";
			if (!description)
				description = "";
		}
	},

    /**
     * This method is called when the HTTP request to get the requested stencil
     * set succeeds. The response is supposed to be a JSON representation
     * according to the stencil set specification.
     * @param {Object} response The JSON representation according to the
     * 			stencil set specification.
     */
	_init: function (response) {

		// init and check consistency.
		this.__handleStencilset(response);

		var pps = new Hash();

		// init property packages
		if (this._jsonObject.propertyPackages) {
			$A(this._jsonObject.propertyPackages).each((function (pp) {
				pps[pp.name] = pp.properties;
			}).bind(this));
		}

		var defaultPosition = 0;

		// init each stencil
		$A(this._jsonObject.stencils).each((function (stencil) {
			defaultPosition++;

			// instantiate normally.
			var oStencil = new ORYX.Core.StencilSet.Stencil(stencil, this.namespace(), this._baseUrl, this, pps, defaultPosition);
			this._stencils[oStencil.id()] = oStencil;
			this._availableStencils[oStencil.id()] = oStencil;

		}).bind(this));
	},

	_cancelInit: function (response) {
		this.errornous = true;
	},

	toString: function () {
		return "StencilSet " + this.title() + " (" + this.namespace() + ")";
	}
});
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespace
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.StencilSet) { ORYX.Core.StencilSet = {}; }

/**
 * Class StencilSets
 * uses Prototpye 1.5.0
 * uses Inheritance
 *
 * Singleton
 */
//storage for loaded stencil sets by namespace
ORYX.Core.StencilSet._stencilSetsByNamespace = new Hash();

//storage for stencil sets by url
ORYX.Core.StencilSet._stencilSetsByUrl = new Hash();

//storage for stencil set namespaces by editor instances
ORYX.Core.StencilSet._StencilSetNSByEditorInstance = new Hash();

//storage for rules by editor instances
ORYX.Core.StencilSet._rulesByEditorInstance = new Hash();

/**
 * 
 * @param {String} editorId
 * 
 * @return {Hash} Returns a hash map with all stencil sets that are loaded by
 * 					the editor with the editorId.
 */
ORYX.Core.StencilSet.stencilSets = function (editorId) {
	var stencilSetNSs = ORYX.Core.StencilSet._StencilSetNSByEditorInstance[editorId];
	var stencilSets = new Hash();
	if (stencilSetNSs) {
		stencilSetNSs.each(function (stencilSetNS) {
			var stencilSet = ORYX.Core.StencilSet.stencilSet(stencilSetNS)
			stencilSets[stencilSet.namespace()] = stencilSet;
		});
	}
	return stencilSets;
};

/**
 * 
 * @param {String} namespace
 * 
 * @return {ORYX.Core.StencilSet.StencilSet} Returns the stencil set with the specified
 * 										namespace.
 * 
 * The method can handle namespace strings like
 *  http://www.example.org/stencilset
 *  http://www.example.org/stencilset#
 *  http://www.example.org/stencilset#ANode
 */
ORYX.Core.StencilSet.stencilSet = function (namespace) {
	ORYX.Log.trace("获取 stencil set %0", namespace);
	var splitted = namespace.split("#", 1);
	if (splitted.length === 1) {
		ORYX.Log.trace("获取 stencil set %0", splitted[0]);
		return ORYX.Core.StencilSet._stencilSetsByNamespace[splitted[0] + "#"];
	} else {
		return undefined;
	}
};

/**
 * 
 * @param {String} id
 * 
 * @return {ORYX.Core.StencilSet.Stencil} Returns the stencil specified by the id.
 * 
 * The id must be unique and contains the namespace of the stencil's stencil set.
 * e.g. http://www.example.org/stencilset#ANode
 */
ORYX.Core.StencilSet.stencil = function (id) {
	ORYX.Log.trace("获取 stencil for %0", id);
	var ss = ORYX.Core.StencilSet.stencilSet(id);
	if (ss) {
		return ss.stencil(id);
	} else {
		ORYX.Log.trace("Cannot fild stencil for %0", id);
		return undefined;
	}
};

/**
 * 
 * @param {String} editorId
 * 
 * @return {ORYX.Core.StencilSet.Rules} Returns the rules object for the editor
 * 									specified by its editor id.
 */
ORYX.Core.StencilSet.rules = function (editorId) {
	if (!ORYX.Core.StencilSet._rulesByEditorInstance[editorId]) {
		ORYX.Core.StencilSet._rulesByEditorInstance[editorId] = new ORYX.Core.StencilSet.Rules();;
	}
	return ORYX.Core.StencilSet._rulesByEditorInstance[editorId];
};

/**
 * 
 * @param {String} url
 * @param {String} editorId
 * 
 * Loads a stencil set from url, if it is not already loaded.
 * It also stores which editor instance loads the stencil set and 
 * initializes the Rules object for the editor instance.
 */
ORYX.Core.StencilSet.loadStencilSet = function (url, editorId) {
	var stencilSet = ORYX.Core.StencilSet._stencilSetsByUrl[url];

	if (!stencilSet) {
		//load stencil set
		stencilSet = new ORYX.Core.StencilSet.StencilSet(url);

		//store stencil set
		ORYX.Core.StencilSet._stencilSetsByNamespace[stencilSet.namespace()] = stencilSet;

		//store stencil set by url
		ORYX.Core.StencilSet._stencilSetsByUrl[url] = stencilSet;
	}

	var namespace = stencilSet.namespace();

	//store which editorInstance loads the stencil set
	if (ORYX.Core.StencilSet._StencilSetNSByEditorInstance[editorId]) {
		ORYX.Core.StencilSet._StencilSetNSByEditorInstance[editorId].push(namespace);
	} else {
		ORYX.Core.StencilSet._StencilSetNSByEditorInstance[editorId] = [namespace];
	}

	//store the rules for the editor instance
	if (ORYX.Core.StencilSet._rulesByEditorInstance[editorId]) {
		ORYX.Core.StencilSet._rulesByEditorInstance[editorId].initializeRules(stencilSet);
	} else {
		var rules = new ORYX.Core.StencilSet.Rules();
		rules.initializeRules(stencilSet);
		ORYX.Core.StencilSet._rulesByEditorInstance[editorId] = rules;
	}
};

/**
 * Returns the translation of an attribute in jsonObject specified by its name
 * according to navigator.language
 */
ORYX.Core.StencilSet.getTranslation = function (jsonObject, name) {
	var lang = ORYX.I18N.Language.toLowerCase();

	var result = jsonObject[name + "_" + lang];

	if (result)
		return result;

	result = jsonObject[name + "_" + lang.substr(0, 2)];

	if (result)
		return result;

	return jsonObject[name];
};
/**
 * Copyright (c) 2006
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }


/**
 * 命令对象
 */
ORYX.Core.Command = Clazz.extend({

	/**
	 * Constructor
	 */
	construct: function () {

	},

	execute: function () {
		throw "Command.execute() has to be implemented!"
	},

	rollback: function () {
		throw "Command.rollback() has to be implemented!"
	}


});
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }


/**
 * @classDescription With Bounds you can set and get position and size of UIObjects.
 */
ORYX.Core.Bounds = {

	/**
	 * Constructor
	 */
	construct: function () {
		this._changedCallbacks = []; //register a callback with changedCallacks.push(this.method.bind(this));
		this.a = {};
		this.b = {};
		this.set.apply(this, arguments);
		this.suspendChange = false;
		this.changedWhileSuspend = false;
	},

	/**
	 * Calls all registered callbacks.
	 */
	_changed: function (sizeChanged) {
		if (!this.suspendChange) {
			this._changedCallbacks.each(function (callback) {
				callback(this, sizeChanged);
			}.bind(this));
			this.changedWhileSuspend = false;
		} else
			this.changedWhileSuspend = true;
	},

	/**
	 * Registers a callback that is called, if the bounds changes.
	 * @param callback {Function} The callback function.
	 */
	registerCallback: function (callback) {
		if (!this._changedCallbacks.member(callback)) {
			this._changedCallbacks.push(callback);
		}
	},

	/**
	 * Unregisters a callback.
	 * @param callback {Function} The callback function.
	 */
	unregisterCallback: function (callback) {
		this._changedCallbacks = this._changedCallbacks.without(callback);
	},

	/**
	 * Sets position and size of the shape dependent of four coordinates
	 * (set(ax, ay, bx, by);), two points (set({x: ax, y: ay}, {x: bx, y: by});)
	 * or one bound (set({a: {x: ax, y: ay}, b: {x: bx, y: by}});).
	 */
	set: function () {

		var changed = false;

		switch (arguments.length) {

			case 1:
				if (this.a.x !== arguments[0].a.x) {
					changed = true;
					this.a.x = arguments[0].a.x;
				}
				if (this.a.y !== arguments[0].a.y) {
					changed = true;
					this.a.y = arguments[0].a.y;
				}
				if (this.b.x !== arguments[0].b.x) {
					changed = true;
					this.b.x = arguments[0].b.x;
				}
				if (this.b.y !== arguments[0].b.y) {
					changed = true;
					this.b.y = arguments[0].b.y;
				}
				break;

			case 2:
				var ax = Math.min(arguments[0].x, arguments[1].x);
				var ay = Math.min(arguments[0].y, arguments[1].y);
				var bx = Math.max(arguments[0].x, arguments[1].x);
				var by = Math.max(arguments[0].y, arguments[1].y);
				if (this.a.x !== ax) {
					changed = true;
					this.a.x = ax;
				}
				if (this.a.y !== ay) {
					changed = true;
					this.a.y = ay;
				}
				if (this.b.x !== bx) {
					changed = true;
					this.b.x = bx;
				}
				if (this.b.y !== by) {
					changed = true;
					this.b.y = by;
				}
				break;

			case 4:
				var ax = Math.min(arguments[0], arguments[2]);
				var ay = Math.min(arguments[1], arguments[3]);
				var bx = Math.max(arguments[0], arguments[2]);
				var by = Math.max(arguments[1], arguments[3]);
				if (this.a.x !== ax) {
					changed = true;
					this.a.x = ax;
				}
				if (this.a.y !== ay) {
					changed = true;
					this.a.y = ay;
				}
				if (this.b.x !== bx) {
					changed = true;
					this.b.x = bx;
				}
				if (this.b.y !== by) {
					changed = true;
					this.b.y = by;
				}
				break;
		}

		if (changed) {
			this._changed(true);
		}
	},

	/**
	 * Moves the bounds so that the point p will be the new upper left corner.
	 * @param {Point} p
	 * or
	 * @param {Number} x
	 * @param {Number} y
	 */
	moveTo: function () {

		var currentPosition = this.upperLeft();
		switch (arguments.length) {
			case 1:
				this.moveBy({
					x: arguments[0].x - currentPosition.x,
					y: arguments[0].y - currentPosition.y
				});
				break;
			case 2:
				this.moveBy({
					x: arguments[0] - currentPosition.x,
					y: arguments[1] - currentPosition.y
				});
				break;
			default:
			//TODO error
		}

	},

	/**
	 * Moves the bounds relatively by p.
	 * @param {Point} p
	 * or
	 * @param {Number} x
	 * @param {Number} y
	 * 
	 */
	moveBy: function () {
		var changed = false;

		switch (arguments.length) {
			case 1:
				var p = arguments[0];
				if (p.x !== 0 || p.y !== 0) {
					changed = true;
					this.a.x += p.x;
					this.b.x += p.x;
					this.a.y += p.y;
					this.b.y += p.y;
				}
				break;
			case 2:
				var x = arguments[0];
				var y = arguments[1];
				if (x !== 0 || y !== 0) {
					changed = true;
					this.a.x += x;
					this.b.x += x;
					this.a.y += y;
					this.b.y += y;
				}
				break;
			default:
			//TODO error
		}

		if (changed) {
			this._changed();
		}
	},

	/***
	 * Includes the bounds b into the current bounds.
	 * @param {Bounds} b
	 */
	include: function (b) {

		if ((this.a.x === undefined) && (this.a.y === undefined) &&
			(this.b.x === undefined) && (this.b.y === undefined)) {
			return b;
		};

		var cx = Math.min(this.a.x, b.a.x);
		var cy = Math.min(this.a.y, b.a.y);

		var dx = Math.max(this.b.x, b.b.x);
		var dy = Math.max(this.b.y, b.b.y);


		this.set(cx, cy, dx, dy);
	},

	/**
	 * Relatively extends the bounds by p.
	 * @param {Point} p
	 */
	extend: function (p) {

		if (p.x !== 0 || p.y !== 0) {
			// this is over cross for the case that a and b have same coordinates.
			//((this.a.x > this.b.x) ? this.a : this.b).x += p.x;
			//((this.b.y > this.a.y) ? this.b : this.a).y += p.y;
			this.b.x += p.x;
			this.b.y += p.y;

			this._changed(true);
		}
	},

	/**
	 * Widens the scope of the bounds by x.
	 * @param {Number} x
	 */
	widen: function (x) {
		if (x !== 0) {
			this.suspendChange = true;
			this.moveBy({ x: -x, y: -x });
			this.extend({ x: 2 * x, y: 2 * x });
			this.suspendChange = false;
			if (this.changedWhileSuspend) {
				this._changed(true);
			}
		}
	},

	/**
	 * Returns the upper left corner's point regardless of the
	 * bound delimiter points.
	 */
	upperLeft: function () {

		return { x: this.a.x, y: this.a.y };
	},

	/**
	 * Returns the lower Right left corner's point regardless of the
	 * bound delimiter points.
	 */
	lowerRight: function () {

		return { x: this.b.x, y: this.b.y };
	},

	/**
	 * @return {Number} Width of bounds.
	 */
	width: function () {
		return this.b.x - this.a.x;
	},

	/**
	 * @return {Number} Height of bounds.
	 */
	height: function () {
		return this.b.y - this.a.y;
	},

	/**
	 * @return {Point} The center point of this bounds.
	 */
	center: function () {
		return {
			x: (this.a.x + this.b.x) / 2.0,
			y: (this.a.y + this.b.y) / 2.0
		};
	},


	/**
	 * @return {Point} The center point of this bounds relative to upperLeft.
	 */
	midPoint: function () {
		return {
			x: (this.b.x - this.a.x) / 2.0,
			y: (this.b.y - this.a.y) / 2.0
		};
	},

	/**
	 * Moves the center point of this bounds to the new position.
	 * @param p {Point} 
	 * or
	 * @param x {Number}
	 * @param y {Number}
	 */
	centerMoveTo: function () {
		var currentPosition = this.center();

		switch (arguments.length) {

			case 1:
				this.moveBy(arguments[0].x - currentPosition.x,
					arguments[0].y - currentPosition.y);
				break;

			case 2:
				this.moveBy(arguments[0] - currentPosition.x,
					arguments[1] - currentPosition.y);
				break;
		}
	},

	isIncluded: function (point, offset) {

		var pointX, pointY, offset;

		// Get the the two Points	
		switch (arguments.length) {
			case 1:
				pointX = arguments[0].x;
				pointY = arguments[0].y;
				offset = 0;

				break;
			case 2:
				if (arguments[0].x && arguments[0].y) {
					pointX = arguments[0].x;
					pointY = arguments[0].y;
					offset = Math.abs(arguments[1]);
				} else {
					pointX = arguments[0];
					pointY = arguments[1];
					offset = 0;
				}
				break;
			case 3:
				pointX = arguments[0];
				pointY = arguments[1];
				offset = Math.abs(arguments[2]);
				break;
			default:
				throw "isIncluded needs one, two or three arguments";
		}

		var ul = this.upperLeft();
		var lr = this.lowerRight();

		if (pointX >= ul.x - offset
			&& pointX <= lr.x + offset && pointY >= ul.y - offset
			&& pointY <= lr.y + offset)
			return true;
		else
			return false;
	},

	/**
	 * @return {Bounds} A copy of this bounds.
	 */
	clone: function () {

		//Returns a new bounds object without the callback
		// references of the original bounds
		return new ORYX.Core.Bounds(this);
	},

	toString: function () {

		return "( " + this.a.x + " | " + this.a.y + " )/( " + this.b.x + " | " + this.b.y + " )";
	},

	serializeForERDF: function () {

		return this.a.x + "," + this.a.y + "," + this.b.x + "," + this.b.y;
	}
};

ORYX.Core.Bounds = Clazz.extend(ORYX.Core.Bounds);
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }


/**
 * @classDescription Abstract base class for all objects that have a graphical representation
 * within the editor.
 * @extends Clazz
 */
ORYX.Core.UIObject = {
	/**
	 * Constructor of the UIObject class.
	 */
	construct: function (options) {

		this.isChanged = true;			//Flag, if UIObject has been changed since last update.
		this.isResized = true;
		this.isVisible = true;			//Flag, if UIObject's display attribute is set to 'inherit' or 'none'
		this.isSelectable = false;		//Flag, if UIObject is selectable.
		this.isResizable = false;		//Flag, if UIObject is resizable.
		this.isMovable = false;			//Flag, if UIObject is movable.

		this.id = ORYX.Editor.provideId();	//get unique id
		this.parent = undefined;		//parent is defined, if this object is added to another uiObject.
		this.node = undefined;			//this is a reference to the SVG representation, either locally or in DOM.
		this.children = [];				//array for all add uiObjects

		this.bounds = new ORYX.Core.Bounds();		//bounds with undefined values

		this._changedCallback = this._changed.bind(this);	//callback reference for calling _changed
		this.bounds.registerCallback(this._changedCallback);	//set callback in bounds

		if (options && options.eventHandlerCallback) {
			this.eventHandlerCallback = options.eventHandlerCallback;
		}
	},

	/**
	 * Sets isChanged flag to true. Callback for the bounds object.
	 */
	_changed: function (bounds, isResized) {
		this.isChanged = true;
		if (this.bounds == bounds)
			this.isResized = isResized || this.isResized;
	},

	/**
	 * If something changed, this method calls the refresh method that must be implemented by subclasses.
	 */
	update: function () {
		if (this.isChanged) {
			this.refresh();
			this.isChanged = false;

			//call update of all children
			this.children.each(function (value) {
				value.update();
			});
		}
	},

	/**
	 * Is called in update method, if isChanged is set to true. Sub classes should call the super class method.
	 */
	refresh: function () {

	},

	/**
	 * @return {Array} Array of all child UIObjects.
	 */
	getChildren: function () {
		return this.children.clone();
	},

	/**
	 * @return {Array} Array of all parent UIObjects.
	 */
	getParents: function () {
		var parents = [];
		var parent = this.parent;
		while (parent) {
			parents.push(parent);
			parent = parent.parent;
		}
		return parents;
	},

	/**
	 * Returns TRUE if the given parent is one of the UIObjects parents or the UIObject themselves, otherwise FALSE.
	 * @param {UIObject} parent
	 * @return {Boolean} 
	 */
	isParent: function (parent) {
		var cparent = this;
		while (cparent) {
			if (cparent === parent) {
				return true;
			}
			cparent = cparent.parent;
		}
		return false;
	},

	/**
	 * @return {String} Id of this UIObject
	 */
	getId: function () {
		return this.id;
	},

	/**
	 * Method for accessing child uiObjects by id.
	 * @param {String} id
	 * @param {Boolean} deep
	 * 
	 * @return {UIObject} If found, it returns the UIObject with id.
	 */
	getChildById: function (id, deep) {
		return this.children.find(function (uiObj) {
			if (uiObj.getId() === id) {
				return uiObj;
			} else {
				if (deep) {
					var obj = uiObj.getChildById(id, deep);
					if (obj) {
						return obj;
					}
				}
			}
		});
	},

	/**
	 * Adds an UIObject to this UIObject and sets the parent of the
	 * added UIObject. It is also added to the SVG representation of this
	 * UIObject.
	 * @param {UIObject} uiObject
	 */
	add: function (uiObject) {
		//add uiObject, if it is not already a child of this object
		if (!(this.children.member(uiObject))) {
			//if uiObject is child of another parent, remove it from that parent.
			if (uiObject.parent) {
				uiObject.remove(uiObject);
			}

			//add uiObject to children
			this.children.push(uiObject);

			//set parent reference
			uiObject.parent = this;

			//add uiObject.node to this.node
			uiObject.node = this.node.appendChild(uiObject.node);

			//register callback to get informed, if child is changed
			uiObject.bounds.registerCallback(this._changedCallback);

			//uiObject.update();
		} else {
			ORYX.Log.info("add: ORYX.Core.UIObject is already a child of this object.");
		}
	},

	/**
	 * Removes UIObject from this UIObject. The SVG representation will also
	 * be removed from this UIObject's SVG representation.
	 * @param {UIObject} uiObject
	 */
	remove: function (uiObject) {
		//if uiObject is a child of this object, remove it.
		if (this.children.member(uiObject)) {
			//remove uiObject from children
			this.children = this._uiObjects.without(uiObject);

			//delete parent reference of uiObject
			uiObject.parent = undefined;

			//delete uiObject.node from this.node
			uiObject.node = this.node.removeChild(uiObject.node);

			//unregister callback to get informed, if child is changed
			uiObject.bounds.unregisterCallback(this._changedCallback);
		} else {
			ORYX.Log.info("remove: ORYX.Core.UIObject is not a child of this object.");
		}

	},

	/**
	 * Calculates absolute bounds of this UIObject.
	 */
	absoluteBounds: function () {
		if (this.parent) {
			var absUL = this.absoluteXY();
			return new ORYX.Core.Bounds(absUL.x, absUL.y,
				absUL.x + this.bounds.width(),
				absUL.y + this.bounds.height());
		} else {
			return this.bounds.clone();
		}
	},

	/**
	 * @return {Point} The absolute position of this UIObject.
	 */
	absoluteXY: function () {
		if (this.parent) {
			var pXY = this.parent.absoluteXY();
			return { x: pXY.x + this.bounds.upperLeft().x, y: pXY.y + this.bounds.upperLeft().y };

		} else {
			return { x: this.bounds.upperLeft().x, y: this.bounds.upperLeft().y };
		}
	},

	/**
	 * @return {Point} The absolute position from the Center of this UIObject.
	 */
	absoluteCenterXY: function () {
		if (this.parent) {
			var pXY = this.parent.absoluteXY();
			return { x: pXY.x + this.bounds.center().x, y: pXY.y + this.bounds.center().y };

		} else {
			return { x: this.bounds.center().x, y: this.bounds.center().y };
		}
	},

	/**
	 * Hides this UIObject and all its children.
	 */
	hide: function () {
		this.node.setAttributeNS(null, 'display', 'none');
		this.isVisible = false;
		this.children.each(function (uiObj) {
			uiObj.hide();
		});
	},

	/**
	 * Enables visibility of this UIObject and all its children.
	 */
	show: function () {
		this.node.setAttributeNS(null, 'display', 'inherit');
		this.isVisible = true;
		this.children.each(function (uiObj) {
			uiObj.show();
		});
	},

	addEventHandlers: function (node) {

		node.addEventListener(ORYX.CONFIG.EVENT_MOUSEDOWN, this._delegateEvent.bind(this), false);
		node.addEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this._delegateEvent.bind(this), false);
		node.addEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this._delegateEvent.bind(this), false);
		node.addEventListener(ORYX.CONFIG.EVENT_MOUSEOVER, this._delegateEvent.bind(this), false);
		node.addEventListener(ORYX.CONFIG.EVENT_MOUSEOUT, this._delegateEvent.bind(this), false);
		node.addEventListener('click', this._delegateEvent.bind(this), false);
		node.addEventListener(ORYX.CONFIG.EVENT_DBLCLICK, this._delegateEvent.bind(this), false);

	},

	_delegateEvent: function (event) {
		if (this.eventHandlerCallback) {
			this.eventHandlerCallback(event, this);
		}
	},

	toString: function () { return "UIObject " + this.id }
};
ORYX.Core.UIObject = Clazz.extend(ORYX.Core.UIObject);
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }


if (!ORYX) { var ORYX = {}; }


//辅助类别create by gk 用于代码复用公用函数
var Helper = {

	construct: function () {

	},
	Ajax: function (url, method, async, callback) {
		if (async != undefined && async) {
			var jsondata = undefined;
			Ext.Ajax.request({
				url: url,
				method: method,
				async: true,
				success: function (response, opts) {
					jsondata = eval('(' + response.responseText + ')');
				}
			});
			return jsondata;
		}
		if (async == undefined || !async) {
			var jsondata = undefined;
			Ext.Ajax.request({
				url: url,
				method: method,
				success: function (response, opts) {
					callback(response, opts)
				}
			});
		}
	},
	getJsonStore: function (url, fields, root) {
		var jsondata = this.Ajax(url, 'GET', true);
		return new Ext.data.JsonStore({
			autoLoad: true,
			async: true,
			fields: fields,
			data: jsondata,
			root: root
		});
	}
};


/**
 * Top Level uiobject.
 * @class ORYX.Core.AbstractShape
 * @extends ORYX.Core.UIObject
 */
ORYX.Core.AbstractShape = ORYX.Core.UIObject.extend(
	/** @lends ORYX.Core.AbstractShape.prototype */
	{

		/**
		 * Constructor
		 */
		construct: function (options, stencil) {

			arguments.callee.$.construct.apply(this, arguments);

			this.resourceId = ORYX.Editor.provideId(); //Id of resource in DOM

			// stencil reference
			this._stencil = stencil;
			// if the stencil defines a super stencil that should be used for its instances, set it.
			if (this._stencil._jsonStencil.superId) {
				stencilId = this._stencil.id()
				superStencilId = stencilId.substring(0, stencilId.indexOf("#") + 1) + stencil._jsonStencil.superId;
				stencilSet = this._stencil.stencilSet();
				this._stencil = stencilSet.stencil(superStencilId);
			}

			//Hash map for all properties. Only stores the values of the properties.
			this.properties = new Hash();
			this.propertiesChanged = new Hash();

			// List of properties which are not included in the stencilset, 
			// but which gets (de)serialized
			this.hiddenProperties = new Hash();


			//Initialization of property map and initial value.
			this._stencil.properties().each((function (property) {
				var key = property.prefix() + "-" + property.id();
				this.properties[key] = property.value();
				this.propertiesChanged[key] = true;
			}).bind(this));

			// if super stencil was defined, also regard stencil's properties:
			if (stencil._jsonStencil.superId) {
				stencil.properties().each((function (property) {
					var key = property.prefix() + "-" + property.id();
					var value = property.value();
					var oldValue = this.properties[key];
					this.properties[key] = value;
					this.propertiesChanged[key] = true;

					// Raise an event, to show that the property has changed
					// required for plugins like processLink.js
					//window.setTimeout( function(){

					this._delegateEvent({
						type: ORYX.CONFIG.EVENT_PROPERTY_CHANGED,
						name: key,
						value: value,
						oldValue: oldValue
					});

					//}.bind(this), 10)

				}).bind(this));
			}

		},

		layout: function () {

		},

		/**
		 * Returns the stencil object specifiing the type of the shape.
		 */
		getStencil: function () {
			return this._stencil;
		},

		/**
		 * 
		 * @param {Object} resourceId
		 */
		getChildShapeByResourceId: function (resourceId) {

			resourceId = ERDF.__stripHashes(resourceId);

			return this.getChildShapes(true).find(function (shape) {
				return shape.resourceId == resourceId
			});
		},
		/**
		 * 
		 * @param {Object} deep
		 * @param {Object} iterator
		 */
		getChildShapes: function (deep, iterator) {
			var result = [];

			this.children.each(function (uiObject) {
				if (uiObject instanceof ORYX.Core.Shape && uiObject.isVisible) {
					if (iterator) {
						iterator(uiObject);
					}
					result.push(uiObject);
					if (deep) {
						result = result.concat(uiObject.getChildShapes(deep, iterator));
					}
				}
			});

			return result;
		},

		/**
		 * @param {Object} shape
		 * @return {boolean} true if any of shape's childs is given shape
		 */
		hasChildShape: function (shape) {
			return this.getChildShapes().any(function (child) {
				return (child === shape) || child.hasChildShape(shape);
			});
		},

		/**
		 * 
		 * @param {Object} deep
		 * @param {Object} iterator
		 */
		getChildNodes: function (deep, iterator) {
			var result = [];

			this.children.each(function (uiObject) {
				if (uiObject instanceof ORYX.Core.Node && uiObject.isVisible) {
					if (iterator) {
						iterator(uiObject);
					}
					result.push(uiObject);
				}
				if (uiObject instanceof ORYX.Core.Shape) {
					if (deep) {
						result = result.concat(uiObject.getChildNodes(deep, iterator));
					}
				}
			});

			return result;
		},

		/**
		 * 
		 * @param {Object} deep
		 * @param {Object} iterator
		 */
		getChildEdges: function (deep, iterator) {
			var result = [];

			this.children.each(function (uiObject) {
				if (uiObject instanceof ORYX.Core.Edge && uiObject.isVisible) {
					if (iterator) {
						iterator(uiObject);
					}
					result.push(uiObject);
				}
				if (uiObject instanceof ORYX.Core.Shape) {
					if (deep) {
						result = result.concat(uiObject.getChildEdges(deep, iterator));
					}
				}
			});

			return result;
		},

		/**
		 * Returns a sorted array of ORYX.Core.Node objects.
		 * Ordered in z Order, the last object has the highest z Order.
		 */
		//TODO deep iterator
		getAbstractShapesAtPosition: function () {
			var x, y;
			switch (arguments.length) {
				case 1:
					x = arguments[0].x;
					y = arguments[0].y;
					break;
				case 2:	//two or more arguments
					x = arguments[0];
					y = arguments[1];
					break;
				default:
					throw "getAbstractShapesAtPosition needs 1 or 2 arguments!"
			}

			if (this.isPointIncluded(x, y)) {

				var result = [];
				result.push(this);

				//check, if one child is at that position						


				var childNodes = this.getChildNodes();
				var childEdges = this.getChildEdges();

				[childNodes, childEdges].each(function (ne) {
					var nodesAtPosition = new Hash();

					ne.each(function (node) {
						if (!node.isVisible) { return }
						var candidates = node.getAbstractShapesAtPosition(x, y);
						if (candidates.length > 0) {
							var nodesInZOrder = $A(node.node.parentNode.childNodes);
							var zOrderIndex = nodesInZOrder.indexOf(node.node);
							nodesAtPosition[zOrderIndex] = candidates;
						}
					});

					nodesAtPosition.keys().sort().each(function (key) {
						result = result.concat(nodesAtPosition[key]);
					});
				});

				return result;

			} else {
				return [];
			}
		},

		/**
		 * 
		 * @param key {String} Must be 'prefix-id' of property
		 * @param value {Object} Can be of type String or Number according to property type.
		 */
		setProperty: function (key, value, force) {
			var oldValue = this.properties[key];
			if (oldValue !== value || force === true) {
				this.properties[key] = value;
				this.propertiesChanged[key] = true;
				this._changed();

				// Raise an event, to show that the property has changed
				//window.setTimeout( function(){

				if (!this._isInSetProperty) {
					this._isInSetProperty = true;

					this._delegateEvent({
						type: ORYX.CONFIG.EVENT_PROPERTY_CHANGED,
						elements: [this],
						name: key,
						value: value,
						oldValue: oldValue
					});

					delete this._isInSetProperty;
				}
				//}.bind(this), 10)
			}
		},

		/**
		 * Returns TRUE if one of the properties is flagged as dirty
		 * @return {boolean}
		 */
		isPropertyChanged: function () {
			return this.propertiesChanged.any(function (property) { return property.value });
		},

		/**
		 * 
		 * @param {String} Must be 'prefix-id' of property
		 * @param {Object} Can be of type String or Number according to property type.
		 */
		setHiddenProperty: function (key, value) {
			// IF undefined, Delete
			if (value === undefined) {
				delete this.hiddenProperties[key];
				return;
			}
			var oldValue = this.hiddenProperties[key];
			if (oldValue !== value) {
				this.hiddenProperties[key] = value;
			}
		},
		/**
		 * Calculate if the point is inside the Shape
		 * @param {Point}
		 */
		isPointIncluded: function (pointX, pointY, absoluteBounds) {
			var absBounds = absoluteBounds ? absoluteBounds : this.absoluteBounds();
			return absBounds.isIncluded(pointX, pointY);

		},

		/**
		 * Get the serialized object
		 * return Array with hash-entrees (prefix, name, value)
		 * Following values will given:
		 * 		Type
		 * 		Properties
		 */
		serialize: function () {
			var serializedObject = [];

			// Add the type
			serializedObject.push({ name: 'type', prefix: 'oryx', value: this.getStencil().id(), type: 'literal' });

			// Add hidden properties
			this.hiddenProperties.each(function (prop) {
				serializedObject.push({ name: prop.key.replace("oryx-", ""), prefix: "oryx", value: prop.value, type: 'literal' });
			}.bind(this));

			// Add all properties
			this.getStencil().properties().each((function (property) {

				var prefix = property.prefix();	// Get prefix
				var name = property.id();		// Get name

				//if(typeof this.properties[prefix+'-'+name] == 'boolean' || this.properties[prefix+'-'+name] != "")
				serializedObject.push({ name: name, prefix: prefix, value: this.properties[prefix + '-' + name], type: 'literal' });

			}).bind(this));

			return serializedObject;
		},


		deserialize: function (serialize) {
			// Search in Serialize
			var initializedDocker = 0;

			// Sort properties so that the hidden properties are first in the list
			serialize = serialize.sort(function (a, b) { a = Number(this.properties.keys().member(a.prefix + "-" + a.name)); b = Number(this.properties.keys().member(b.prefix + "-" + b.name)); return a > b ? 1 : (a < b ? -1 : 0) }.bind(this));

			serialize.each((function (obj) {

				var name = obj.name;
				var prefix = obj.prefix;
				var value = obj.value;

				// Complex properties can be real json objects, encode them to a string
				if (Ext.type(value) === "object") value = Ext.encode(value);

				switch (prefix + "-" + name) {
					case 'raziel-parent':
						// Set parent
						if (!this.parent) { break };

						// Set outgoing Shape
						var parent = this.getCanvas().getChildShapeByResourceId(value);
						if (parent) {
							parent.add(this);
						}

						break;
					default:
						// If list, eval as an array
						var prop = this.getStencil().property(prefix + "-" + name);
						if (prop && prop.isList() && typeof value === "string") {
							if ((value || "").strip() && !value.startsWith("[") && !value.startsWith("]"))
								value = "[\"" + value.strip() + "\"]";
							value = ((value || "").strip() || "[]").evalJSON();
						}

						// Set property
						if (this.properties.keys().member(prefix + "-" + name)) {
							this.setProperty(prefix + "-" + name, value);
						} else if (!(name === "bounds" || name === "parent" || name === "target" || name === "dockers" || name === "docker" || name === "outgoing" || name === "incoming")) {
							this.setHiddenProperty(prefix + "-" + name, value);
						}

				}
			}).bind(this));
		},

		toString: function () { return "ORYX.Core.AbstractShape " + this.id },

		/**
		 * Converts the shape to a JSON representation.
		 * @return {Object} A JSON object with included ORYX.Core.AbstractShape.JSONHelper and getShape() method.
		 */
		toJSON: function () {
			var json = {
				resourceId: this.resourceId,
				properties: Ext.apply({}, this.properties, this.hiddenProperties).inject({}, function (props, prop) {
					var key = prop[0];
					var value = prop[1];

					//If complex property, value should be a json object
					if (this.getStencil().property(key)
						&& this.getStencil().property(key).type() === ORYX.CONFIG.TYPE_COMPLEX
						&& Ext.type(value) === "string") {

						try { value = Ext.decode(value); } catch (error) { }

						// Parse date
					} else if (value instanceof Date && this.getStencil().property(key)) {
						try {
							value = value.format(this.getStencil().property(key).dateFormat());
						} catch (e) { }
					}

					//Takes "my_property" instead of "oryx-my_property" as key
					key = key.replace(/^[\w_]+-/, "");
					props[key] = value;

					return props;
				}.bind(this)),
				stencil: {
					id: this.getStencil().idWithoutNs()
				},
				childShapes: this.getChildShapes().map(function (shape) {
					return shape.toJSON()
				})
			};

			if (this.getOutgoingShapes) {
				json.outgoing = this.getOutgoingShapes().map(function (shape) {
					return {
						resourceId: shape.resourceId
					};
				});
			}

			if (this.bounds) {
				json.bounds = {
					lowerRight: this.bounds.lowerRight(),
					upperLeft: this.bounds.upperLeft()
				};
			}

			if (this.dockers) {
				json.dockers = this.dockers.map(function (docker) {
					var d = docker.getDockedShape() && docker.referencePoint ? docker.referencePoint : docker.bounds.center();
					d.getDocker = function () { return docker; };
					return d;
				})
			}

			Ext.apply(json, ORYX.Core.AbstractShape.JSONHelper);

			// do not pollute the json attributes (for serialization), so put the corresponding
			// shape is encapsulated in a method
			json.getShape = function () {
				return this;
			}.bind(this);

			return json;
		}
	});

/**
 * @namespace Collection of methods which can be used on a shape json object (ORYX.Core.AbstractShape#toJSON()).
 * @example
 * Ext.apply(shapeAsJson, ORYX.Core.AbstractShape.JSONHelper);
 */
ORYX.Core.AbstractShape.JSONHelper = {
	/**
	 * Iterates over each child shape.
	 * @param {Object} iterator Iterator function getting a child shape and his parent as arguments.
	 * @param {boolean} [deep=false] Iterate recursively (childShapes of childShapes)
	 * @param {boolean} [modify=false] If true, the result of the iterator function is taken as new shape, return false to delete it. This enables modifying the object while iterating through the child shapes.
	 * @example
	 * // Increases the lowerRight x value of each direct child shape by one. 
	 * myShapeAsJson.eachChild(function(shape, parentShape){
	 *     shape.bounds.lowerRight.x = shape.bounds.lowerRight.x + 1;
	 *     return shape;
	 * }, false, true);
	 */
	eachChild: function (iterator, deep, modify) {
		if (!this.childShapes) return;

		var newChildShapes = []; //needed if modify = true

		this.childShapes.each(function (shape) {
			if (!(shape.eachChild instanceof Function)) {
				Ext.apply(shape, ORYX.Core.AbstractShape.JSONHelper);
			}
			var res = iterator(shape, this);
			if (res) newChildShapes.push(res); //if false is returned, and modify = true, current shape is deleted.

			if (deep) shape.eachChild(iterator, deep, modify);
		}.bind(this));

		if (modify) this.childShapes = newChildShapes;
	},

	getShape: function () {
		return null;
	},
	getChildShapes: function (deep) {
		var allShapes = this.childShapes;

		if (deep) {
			this.eachChild(function (shape) {
				if (!(shape.getChildShapes instanceof Function)) {
					Ext.apply(shape, ORYX.Core.AbstractShape.JSONHelper);
				}
				allShapes = allShapes.concat(shape.getChildShapes(deep));
			}, true);
		}

		return allShapes;
	},

	/**
	 * @return {String} Serialized JSON object
	 */
	serialize: function () {
		return Ext.encode(this);
	}
}
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }

/**
   @namespace Namespace for the Oryx core elements.
   @name ORYX.Core
*/
if (!ORYX.Core) { ORYX.Core = {}; }

/**
 * @class Oryx canvas.
 * @extends ORYX.Core.AbstractShape
 *
 */
ORYX.Core.Canvas = ORYX.Core.AbstractShape.extend({
	/** @lends ORYX.Core.Canvas.prototype */

	/**
	 * Defines the current zoom level
	 */
	zoomLevel: 1,

	/**
	 * Constructor
	 */
	construct: function (options) {
		arguments.callee.$.construct.apply(this, arguments);

		if (!(options && options.width && options.height)) {

			ORYX.Log.fatal("Canvas is missing mandatory parameters options.width and options.height.");
			return;
		}

		//TODO: set document resource id
		this.resourceId = options.id;

		this.nodes = [];

		this.edges = [];

		//init svg document
		this.rootNode = ORYX.Editor.graft("http://www.w3.org/2000/svg", options.parentNode,
			['svg', { id: this.id, width: options.width, height: options.height },
				['defs', {}]
			]);

		this.rootNode.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
		this.rootNode.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg");

		this._htmlContainer = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", options.parentNode,
			['div', { id: "oryx_canvas_htmlContainer", style: "position:absolute; top:5px" }]);

		this.node = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.rootNode,
			['g', {},
				['g', { "class": "stencils" },
					['g', { "class": "me" }],
					['g', { "class": "children" }],
					['g', { "class": "edge" }]
				],
				['g', { "class": "svgcontainer" }]
			]);


		this.node.setAttributeNS(null, 'stroke', 'black');
		this.node.setAttributeNS(null, 'font-family', 'Verdana, sans-serif');
		this.node.setAttributeNS(null, 'font-size-adjust', 'none');
		this.node.setAttributeNS(null, 'font-style', 'normal');
		this.node.setAttributeNS(null, 'font-variant', 'normal');
		this.node.setAttributeNS(null, 'font-weight', 'normal');
		this.node.setAttributeNS(null, 'line-heigth', 'normal');

		this.node.setAttributeNS(null, 'font-size', ORYX.CONFIG.LABEL_DEFAULT_LINE_HEIGHT);

		this.bounds.set(0, 0, options.width, options.height);

		this.addEventHandlers(this.rootNode.parentNode);

		//disable context menu
		this.rootNode.oncontextmenu = function () { return false; };
	},

	getScrollNode: function () {
		return Ext.get(this.rootNode).parent("div{overflow=auto}", true);
	},

	focus: function () {

		try {
			// Get a href
			if (!this.focusEl) {
				this.focusEl = Ext.getBody().createChild({
					tag: "a",
					href: "#",
					cls: "x-grid3-focus x-grid3-focus-canvas",
					tabIndex: "-1"
				});
				this.focusEl.swallowEvent("click", true);
			}

			// Focus it
			if (Ext.isGecko) {
				this.focusEl.focus();
			}
			else {
				this.focusEl.focus.defer(1, this.focusEl);
			}
			this.focusEl.blur.defer(3, this.focusEl);

		} catch (e) { }
	},

	update: function () {

		this.nodes.each(function (node) {
			this._traverseForUpdate(node);
		}.bind(this));

		// call stencil's layout callback
		// (needed for row layouting of xforms)
		//this.getStencil().layout(this);

		var layoutEvents = this.getStencil().layout();

		if (layoutEvents) {
			layoutEvents.each(function (event) {

				// setup additional attributes
				event.shape = this;
				event.forceExecution = true;
				event.target = this.rootNode;

				// do layouting

				this._delegateEvent(event);
			}.bind(this))
		}

		this.nodes.invoke("_update");

		this.edges.invoke("_update", true);

		/*this.children.each(function(child) {
			child._update();
		});*/
	},

	_traverseForUpdate: function (shape) {
		var childRet = shape.isChanged;
		shape.getChildNodes(false, function (child) {
			if (this._traverseForUpdate(child)) {
				childRet = true;
			}
		}.bind(this));

		if (childRet) {
			shape.layout();
			return true;
		} else {
			return false;
		}
	},

	layout: function () {



	},

	/**
	 * 
	 * @param {Object} deep
	 * @param {Object} iterator
	 */
	getChildNodes: function (deep, iterator) {
		if (!deep && !iterator) {
			return this.nodes.clone();
		} else {
			var result = [];
			this.nodes.each(function (uiObject) {
				if (iterator) {
					iterator(uiObject);
				}
				result.push(uiObject);

				if (deep && uiObject instanceof ORYX.Core.Shape) {
					result = result.concat(uiObject.getChildNodes(deep, iterator));
				}
			});

			return result;
		}
	},

	/**
	 * Overrides the UIObject.add method. Adds uiObject to the correct sub node.
	 * @param {UIObject} uiObject
	 */
	add: function (uiObject, index, silent) {
		//if uiObject is child of another UIObject, remove it.
		if (uiObject instanceof ORYX.Core.UIObject) {
			if (!(this.children.member(uiObject))) {
				//if uiObject is child of another parent, remove it from that parent.
				if (uiObject.parent) {
					uiObject.parent.remove(uiObject, true);
				}

				//add uiObject to the Canvas
				//add uiObject to this Shape
				if (index != undefined)
					this.children.splice(index, 0, uiObject);
				else
					this.children.push(uiObject);

				//set parent reference
				uiObject.parent = this;

				//add uiObject.node to this.node depending on the type of uiObject
				if (uiObject instanceof ORYX.Core.Shape) {
					if (uiObject instanceof ORYX.Core.Edge) {
						uiObject.addMarkers(this.rootNode.getElementsByTagNameNS(NAMESPACE_SVG, "defs")[0]);
						uiObject.node = this.node.childNodes[0].childNodes[2].appendChild(uiObject.node);
						this.edges.push(uiObject);
					} else {
						uiObject.node = this.node.childNodes[0].childNodes[1].appendChild(uiObject.node);
						this.nodes.push(uiObject);
					}
				} else {	//UIObject
					uiObject.node = this.node.appendChild(uiObject.node);
				}

				uiObject.bounds.registerCallback(this._changedCallback);

				if (this.eventHandlerCallback && silent !== true)
					this.eventHandlerCallback({ type: ORYX.CONFIG.EVENT_SHAPEADDED, shape: uiObject })
			} else {

				ORYX.Log.warn("add: ORYX.Core.UIObject is already a child of this object.");
			}
		} else {

			ORYX.Log.fatal("add: Parameter is not of type ORYX.Core.UIObject.");
		}
	},

	/**
	 * Overrides the UIObject.remove method. Removes uiObject.
	 * @param {UIObject} uiObject
	 */
	remove: function (uiObject, silent) {
		//if uiObject is a child of this object, remove it.
		if (this.children.member(uiObject)) {
			//remove uiObject from children
			var parent = uiObject.parent;

			this.children = this.children.without(uiObject);

			//delete parent reference of uiObject
			uiObject.parent = undefined;

			//delete uiObject.node from this.node
			if (uiObject instanceof ORYX.Core.Shape) {
				if (uiObject instanceof ORYX.Core.Edge) {
					uiObject.removeMarkers();
					uiObject.node = this.node.childNodes[0].childNodes[2].removeChild(uiObject.node);
					this.edges = this.edges.without(uiObject);
				} else {
					uiObject.node = this.node.childNodes[0].childNodes[1].removeChild(uiObject.node);
					this.nodes = this.nodes.without(uiObject);
				}
			} else {	//UIObject
				uiObject.node = this.node.removeChild(uiObject.node);
			}

			if (this.eventHandlerCallback && silent !== true)
				this.eventHandlerCallback({ type: ORYX.CONFIG.EVENT_SHAPEREMOVED, shape: uiObject, parent: parent });

			uiObject.bounds.unregisterCallback(this._changedCallback);
		} else {

			ORYX.Log.warn("remove: ORYX.Core.UIObject is not a child of this object.");
		}
	},

    /**
     * Creates shapes out of the given collection of shape objects and adds them to the canvas.
     * @example 
     * canvas.addShapeObjects({
         bounds:{ lowerRight:{ y:510, x:633 }, upperLeft:{ y:146, x:210 } },
         resourceId:"oryx_F0715955-50F2-403D-9851-C08CFE70F8BD",
         childShapes:[],
         properties:{},
         stencil:{
           id:"Subprocess"
         },
         outgoing:[{resourceId: 'aShape'}],
         target: {resourceId: 'aShape'}
       });
     * @param {Object} shapeObjects 
     * @param {Function} [eventHandler] An event handler passed to each newly created shape (as eventHandlerCallback)
     * @return {Array} A collection of ORYX.Core.Shape
     * @methodOf ORYX.Core.Canvas.prototype
     */
	addShapeObjects: function (shapeObjects, eventHandler) {
		if (!shapeObjects) return;

		this.initializingShapes = true;

        /*FIXME This implementation is very evil! At first, all shapes are created on
          canvas. In a second step, the attributes are applied. There must be a distinction
          between the configuration phase (where the outgoings, for example, are just named),
          and the creation phase (where the outgoings are evaluated). This must be reflected
          in code to provide a nicer API/ implementation!!! */

		var addShape = function (shape, parent) {
			// Create a new Stencil
			var stencil = ORYX.Core.StencilSet.stencil(this.getStencil().namespace() + shape.stencil.id);

			// Create a new Shape
			var ShapeClass = (stencil.type() == "node") ? ORYX.Core.Node : ORYX.Core.Edge;
			var newShape = new ShapeClass(
				{ 'eventHandlerCallback': eventHandler },
				stencil);

			// Set the resource id
			newShape.resourceId = shape.resourceId;
			newShape.node.id = "svg-" + shape.resourceId;

			// Set parent to json object to be used later
			// Due to the nested json structure, normally shape.parent is not set/ must not be set. 
			// In special cases, it can be easier to set this directly instead of a nested structure.
			shape.parent = "#" + ((shape.parent && shape.parent.resourceId) || parent.resourceId);

			// Add the shape to the canvas
			this.add(newShape);

			return {
				json: shape,
				object: newShape
			};
		}.bind(this);

        /** Builds up recursively a flatted array of shapes, including a javascript object and json representation
         * @param {Object} shape Any object that has Object#childShapes
         */
		var addChildShapesRecursively = function (shape) {
			var addedShapes = [];

			shape.childShapes.each(function (childShape) {
				addedShapes.push(addShape(childShape, shape));
				addedShapes = addedShapes.concat(addChildShapesRecursively(childShape));
			});

			return addedShapes;
		}.bind(this);

		var shapes = addChildShapesRecursively({
			childShapes: shapeObjects,
			resourceId: this.resourceId
		});


		// prepare deserialisation parameter
		shapes.each(
			function (shape) {
				var properties = [];
				for (field in shape.json.properties) {
					properties.push({
						prefix: 'oryx',
						name: field,
						value: shape.json.properties[field]
					});
				}

				// Outgoings
				shape.json.outgoing.each(function (out) {
					properties.push({
						prefix: 'raziel',
						name: 'outgoing',
						value: "#" + out.resourceId
					});
				});

				// Target 
				// (because of a bug, the first outgoing is taken when there is no target,
				// can be removed after some time)
				if (shape.object instanceof ORYX.Core.Edge) {
					var target = shape.json.target || shape.json.outgoing[0];
					if (target) {
						properties.push({
							prefix: 'raziel',
							name: 'target',
							value: "#" + target.resourceId
						});
					}
				}

				// Bounds
				if (shape.json.bounds) {
					properties.push({
						prefix: 'oryx',
						name: 'bounds',
						value: shape.json.bounds.upperLeft.x + "," + shape.json.bounds.upperLeft.y + "," + shape.json.bounds.lowerRight.x + "," + shape.json.bounds.lowerRight.y
					});
				}

				//Dockers [{x:40, y:50}, {x:30, y:60}] => "40 50 30 60  #"
				if (shape.json.dockers) {
					properties.push({
						prefix: 'oryx',
						name: 'dockers',
						value: shape.json.dockers.inject("", function (dockersStr, docker) {
							return dockersStr + docker.x + " " + docker.y + " ";
						}) + " #"
					});
				}

				//Parent
				properties.push({
					prefix: 'raziel',
					name: 'parent',
					value: shape.json.parent
				});

				shape.__properties = properties;
			}.bind(this)
		);

		// Deserialize the properties from the shapes
		// This can't be done earlier because Shape#deserialize expects that all referenced nodes are already there

		// first, deserialize all nodes
		shapes.each(function (shape) {
			if (shape.object instanceof ORYX.Core.Node) {
				shape.object.deserialize(shape.__properties, shape.json);
			}
		});

		// second, deserialize all edges
		shapes.each(function (shape) {
			if (shape.object instanceof ORYX.Core.Edge) {
				shape.object.deserialize(shape.__properties, shape.json);
				shape.object._oldBounds = shape.object.bounds.clone();
				shape.object._update();
			}
		});

		delete this.initializingShapes;
		return shapes.pluck("object");
	},

    /**
     * Updates the size of the canvas, regarding to the containg shapes.
     */
	updateSize: function () {
		// Check the size for the canvas
		var maxWidth = 0;
		var maxHeight = 0;
		var offset = 100;
		this.getChildShapes(true, function (shape) {
			var b = shape.bounds;
			maxWidth = Math.max(maxWidth, b.lowerRight().x + offset)
			maxHeight = Math.max(maxHeight, b.lowerRight().y + offset)
		});

		if (this.bounds.width() < maxWidth || this.bounds.height() < maxHeight) {
			this.setSize({ width: Math.max(this.bounds.width(), maxWidth), height: Math.max(this.bounds.height(), maxHeight) })
		}
	},

	getRootNode: function () {
		return this.rootNode;
	},

	getSvgContainer: function () {
		return this.node.childNodes[1];
	},

	getHTMLContainer: function () {
		return this._htmlContainer;
	},

	/**
	 * Return all elements of the same highest level
	 * @param {Object} elements
	 */
	getShapesWithSharedParent: function (elements) {

		// If there is no elements, return []
		if (!elements || elements.length < 1) { return [] }
		// If there is one element, return this element
		if (elements.length == 1) { return elements }

		return elements.findAll(function (value) {
			var parentShape = value.parent;
			while (parentShape) {
				if (elements.member(parentShape)) return false;
				parentShape = parentShape.parent
			}
			return true;
		});

	},

	setSize: function (size, dontSetBounds) {
		if (!size || !size.width || !size.height) { return }

		if (this.rootNode.parentNode) {
			this.rootNode.parentNode.style.width = size.width + 'px';
			this.rootNode.parentNode.style.height = size.height + 'px';
		}

		this.rootNode.setAttributeNS(null, 'width', size.width);
		this.rootNode.setAttributeNS(null, 'height', size.height);

		//this._htmlContainer.style.top = "-" + (size.height + 4) + 'px';		
		if (!dontSetBounds) {
			this.bounds.set({ a: { x: 0, y: 0 }, b: { x: size.width / this.zoomLevel, y: size.height / this.zoomLevel } })
		}
	},

	/**
	 * Returns an SVG document of the current process.
	 * @param {Boolean} escapeText Use true, if you want to parse it with an XmlParser,
	 * 					false, if you want to use the SVG document in browser on client side.
	 */
	getSVGRepresentation: function (escapeText) {
		// Get the serialized svg image source
		var svgClone = this.getRootNode().cloneNode(true);

		this._removeInvisibleElements(svgClone);

		var x1, y1, x2, y2;
		try {
			var bb = this.getRootNode().childNodes[1].getBBox();
			x1 = bb.x;
			y1 = bb.y;
			x2 = bb.x + bb.width;
			y2 = bb.y + bb.height;
		} catch (e) {
			this.getChildShapes(true).each(function (shape) {
				var absBounds = shape.absoluteBounds();
				var ul = absBounds.upperLeft();
				var lr = absBounds.lowerRight();
				if (x1 == undefined) {
					x1 = ul.x;
					y1 = ul.y;
					x2 = lr.x;
					y2 = lr.y;
				} else {
					x1 = Math.min(x1, ul.x);
					y1 = Math.min(y1, ul.y);
					x2 = Math.max(x2, lr.x);
					y2 = Math.max(y2, lr.y);
				}
			});
		}

		var margin = 50;

		var width, height, tx, ty;
		if (x1 == undefined) {
			width = 0;
			height = 0;
			tx = 0;
			ty = 0;
		} else {
			width = x2 - x1;
			height = y2 - y1;
			tx = -x1 + margin / 2;
			ty = -y1 + margin / 2;
		}



		// Set the width and height
		svgClone.setAttributeNS(null, 'width', width + margin);
		svgClone.setAttributeNS(null, 'height', height + margin);

		svgClone.childNodes[1].firstChild.setAttributeNS(null, 'transform', 'translate(' + tx + ", " + ty + ')');

		//remove scale factor
		svgClone.childNodes[1].removeAttributeNS(null, 'transform');

		try {
			var svgCont = svgClone.childNodes[1].childNodes[1];
			svgCont.parentNode.removeChild(svgCont);
		} catch (e) { }

		if (escapeText) {
			$A(svgClone.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'tspan')).each(function (elem) {
				elem.textContent = elem.textContent.escapeHTML();
			});

			$A(svgClone.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'text')).each(function (elem) {
				if (elem.childNodes.length == 0)
					elem.textContent = elem.textContent.escapeHTML();
			});
		}

		// generating absolute urls for the pdf-exporter
		$A(svgClone.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'image')).each(function (elem) {
			var href = elem.getAttributeNS("http://www.w3.org/1999/xlink", "href");

			if (!href.match("^(http|https)://")) {
				href = window.location.protocol + "//" + window.location.host + href;
				elem.setAttributeNS("http://www.w3.org/1999/xlink", "href", href);
			}
		});


		// escape all links
		$A(svgClone.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'a')).each(function (elem) {
			elem.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", (elem.getAttributeNS("http://www.w3.org/1999/xlink", "href") || "").escapeHTML());
		});

		return svgClone;
	},

	/**   
	* Removes all nodes (and its children) that has the
	* attribute visibility set to "hidden"
	*/
	_removeInvisibleElements: function (element) {
		var index = 0;
		while (index < element.childNodes.length) {
			var child = element.childNodes[index];
			if (child.getAttributeNS &&
				child.getAttributeNS(null, "visibility") === "hidden") {
				element.removeChild(child);
			} else {
				this._removeInvisibleElements(child);
				index++;
			}
		}

	},


	_delegateEvent: function (event) {
		if (this.eventHandlerCallback && (event.target == this.rootNode || event.target == this.rootNode.parentNode)) {
			this.eventHandlerCallback(event, this);
		}
	},

	toString: function () { return "Canvas " + this.id },

    /**
     * Calls {@link ORYX.Core.AbstractShape#toJSON} and adds some stencil set information.
     */
	toJSON: function () {
		var json = arguments.callee.$.toJSON.apply(this, arguments);

		json.stencilset = {
			url: this.getStencil().stencilSet().source(),
			namespace: this.getStencil().stencilSet().namespace()
		};
		return json;
	}
});

/**
* Copyright (c) 2006
* Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
**/

var idCounter = 0;
var ID_PREFIX = "resource";

/**
 * Main initialization method. To be called when loading
 * of the document, including all scripts, is completed.
 */
function init() {

	/* When the blank image url is not set programatically to a local
	 * representation, a spacer gif on the site of ext is loaded from the
	 * internet. This causes problems when internet or the ext site are not
	 * available. */
	Ext.BLANK_IMAGE_URL = (ORYX.CONFIG.BLANK_IMAGE) || (ORYX.PATH + 'libs/ext-2.0.2/resources/images/default/s.gif');

	ORYX.Log.debug("Querying editor instances");

	// Hack for WebKit to set the SVGElement-Classes
	ORYX.Editor.setMissingClasses();

	// If someone wants to create the editor instance himself
	if (window.onOryxResourcesLoaded) {
		window.onOryxResourcesLoaded();
	}
	// Else if this is a newly created model
	else if (window.location.pathname.include(ORYX.CONFIG.ORYX_NEW_URL)) {
		new ORYX.Editor({
			id: 'oryx-canvas123',
			fullscreen: true,
			stencilset: {
				url: "/oryx" + ORYX.Utils.getParamFromUrl("stencilset")
			}
		});
	}
	// Else fetch the model from server and display editor
	else {
		//HACK for distinguishing between different backends
		// Backend of 2008 uses /self URL ending
		var modelUrl = window.location.href.replace(/#.*/g, "");
		if (modelUrl.endsWith("/self")) {
			modelUrl = modelUrl.replace("/self", "/json");
		} else {
			var modelId = getQueryString('id');
			modelUrl = ORYX.CONFIG.SERVLET_HANDLER_ROOT + "/model/" + modelId + "/json";
		}

		ORYX.Editor.createByUrl(modelUrl, {
			id: modelUrl
		});
	}
}

/**
   @namespace Global Oryx name space
   @name ORYX
*/
if (!ORYX) { var ORYX = {}; }

/**
 * The Editor class.
 * 操作画布画流程对象
 * @class ORYX.Editor
 * @extends Clazz
 * @param {Object} config An editor object, passed to {@link ORYX.Editor#loadSerialized}
 * @param {String} config.id Any ID that can be used inside the editor. If fullscreen=false, any HTML node with this id must be present to render the editor to this node.
 * @param {boolean} [config.fullscreen=true] Render editor in fullscreen mode or not.
 * @param {String} config.stencilset.url Stencil set URL.
 * @param {String} [config.stencil.id] Stencil type used for creating the canvas.  
 * @param {Object} config.properties Any properties applied to the canvas.
*/
ORYX.Editor = {
	/** @lends ORYX.Editor.prototype */
	// Defines the global dom event listener 
	DOMEventListeners: new Hash(),

	// Defines the selection
	selection: [],

	// Defines the current zoom level
	zoomLevel: 1.0,

	construct: function (config) {

		// initialization.
		this._eventsQueue = [];
		this.loadedPlugins = [];
		this.pluginsData = [];


		//meta data about the model for the signavio warehouse
		//directory, new, name, description, revision, model (the model data)

		this.modelMetaData = config;

		var model = config;
		if (config.model) {
			model = config.model;
		}

		this.id = model.resourceId;
		if (!this.id) {
			this.id = model.id;
			if (!this.id) {
				this.id = ORYX.Editor.provideId();
			}
		}



		// Defines if the editor should be fullscreen or not
		this.fullscreen = config.fullscreen !== false;

		if (Signavio && Signavio.Helper && Signavio.Helper.ShowMask instanceof Function) {
			Signavio.Helper.ShowMask(true, !this.fullscreen ? this.id : Ext.getBody());
		}

		// Initialize the eventlistener
		this._initEventListener();

		// Load particular stencilset
		if (ORYX.CONFIG.BACKEND_SWITCH) {
			var ssUrl = (model.stencilset.namespace || model.stencilset.url).replace("#", "%23");
			ORYX.Core.StencilSet.loadStencilSet(ORYX.CONFIG.STENCILSET_HANDLER + ssUrl, this.id);
		} else {
			var ssUrl = model.stencilset.url;
			ORYX.Core.StencilSet.loadStencilSet(ssUrl, this.id);
		}

		// CREATES the canvas 要画画咯.
		this._createCanvas(model.stencil ? model.stencil.id : null, model.properties);

		// GENERATES the whole EXT.VIEWPORT
		this._generateGUI();

		// Initializing of a callback to check loading ends
		var loadPluginFinished = false;
		var loadContentFinished = false;
		var initFinished = function () {
			if (!loadPluginFinished || !loadContentFinished) { return }
			this._finishedLoading();
		}.bind(this)

		// disable key events when Ext modal window is active
		ORYX.Editor.makeExtModalWindowKeysave(this._getPluginFacade());

		// LOAD the plugins
		window.setTimeout(function () {
			this.loadPlugins();
			loadPluginFinished = true;
			initFinished();
		}.bind(this), 100);

		// LOAD the content of the current editor instance
		window.setTimeout(function () {
			this.loadSerialized(model, true); // Request the meta data as well
			this.getCanvas().update();
			loadContentFinished = true;
			initFinished();
		}.bind(this), 200);
	},

	_finishedLoading: function () {
		if (Ext.getCmp('oryx-loading-panel')) {
			Ext.getCmp('oryx-loading-panel').hide()
		}

		// Do Layout for viewport
		this.layout.doLayout();
		// Generate a drop target
		new Ext.dd.DropTarget(this.getCanvas().rootNode.parentNode);

		// Fixed the problem that the viewport can not 
		// start with collapsed panels correctly
		if (ORYX.CONFIG.PANEL_RIGHT_COLLAPSED === true) {
			this.layout_regions.east.collapse();
		}
		if (ORYX.CONFIG.PANEL_LEFT_COLLAPSED === true) {
			this.layout_regions.west.collapse();
		}

		// Raise Loaded Event
		this.handleEvents({ type: ORYX.CONFIG.EVENT_LOADED })

	},

	_initEventListener: function () {

		// Register on Events

		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_KEYDOWN, this.catchKeyDownEvents.bind(this), false);
		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_KEYUP, this.catchKeyUpEvents.bind(this), false);

		// Enable Key up and down Event
		this._keydownEnabled = true;
		this._keyupEnabled = true;

		this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEDOWN] = [];
		this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEUP] = [];
		this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEOVER] = [];
		this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEOUT] = [];
		this.DOMEventListeners[ORYX.CONFIG.EVENT_SELECTION_CHANGED] = [];
		this.DOMEventListeners[ORYX.CONFIG.EVENT_MOUSEMOVE] = [];

	},

	/**
	 * Generate the whole viewport of the
	 * Editor and initialized the Ext-Framework
	 * 
	 */
	_generateGUI: function () {

		//TODO make the height be read from eRDF data from the canvas.
		// default, a non-fullscreen editor shall define its height by layout.setHeight(int) 

		// Defines the layout hight if it's NOT fullscreen
		var layoutHeight = ORYX.CONFIG.WINDOW_HEIGHT;

		var canvasParent = this.getCanvas().rootNode.parentNode;

		/**
		 * Extend the Region implementation so that, 
		 * the clicking area can be extend to the whole collapse area and
		 * an title can now be shown.
		 *
		 */
		var oldGetCollapsedEl = Ext.layout.BorderLayout.Region.prototype.getCollapsedEl;
		Ext.layout.BorderLayout.Region.prototype.getCollapsedEl = function () {
			oldGetCollapsedEl.apply(this, arguments);

			if (this.collapseMode !== 'mini' && this.floatable === false && this.expandTriggerAll === true) {
				this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
				this.collapsedEl.on("mouseover", this.collapsedEl.addClass.bind(this.collapsedEl, "x-layout-collapsed-over"));
				this.collapsedEl.on("click", this.onExpandClick, this);
			}


			if (this.collapseTitle) {
				// Use SVG to rotate text
				var svg = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.collapsedEl.dom,
					['svg', { style: "position:relative;left:" + (this.position === "west" ? 4 : 6) + "px;top:" + (this.position === "west" ? 2 : 5) + "px;" },
						['text', { transform: "rotate(90)", x: 0, y: 0, "stroke-width": "0px", fill: "#EEEEEE", style: "font-weight:bold;", "font-size": "11" }, this.collapseTitle]
					]);
				var text = svg.childNodes[0];
				svg.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg");

				// Rotate the west into the other side
				if (this.position === "west" && text.getComputedTextLength instanceof Function) {
					// Wait till rendered
					window.setTimeout(function () {
						var length = text.getComputedTextLength();
						text.setAttributeNS(null, "transform", "rotate(-90, " + ((length / 2) + 7) + ", " + ((length / 2) - 3) + ")");;
					}, 1)
				}
				delete this.collapseTitle;
			}
			return this.collapsedEl;
		}

		// DEFINITION OF THE VIEWPORT AREAS
		this.layout_regions = {

			// DEFINES TOP-AREA
			north: new Ext.Panel({ //TOOO make a composite of the oryx header and addable elements (for toolbar), second one should contain margins
				region: 'north',
				cls: 'x-panel-editor-north',
				autoEl: 'div',
				border: false
			}),

			// DEFINES RIGHT-AREA
			east: new Ext.Panel({
				region: 'east',
				layout: 'fit',
				cls: 'x-panel-editor-east',
				autoEl: 'div',
				collapseTitle: ORYX.I18N.View.East,
				border: false,
				cmargins: { left: 0, right: 0 },
				floatable: false,
				expandTriggerAll: true,
				collapsible: true,
				width: ORYX.CONFIG.PANEL_RIGHT_WIDTH || 200,
				split: true,
				title: "East"
			}),


			// DEFINES BOTTOM-AREA
			south: new Ext.Panel({
				region: 'south',
				cls: 'x-panel-editor-south',
				autoEl: 'div',
				border: false
			}),


			// DEFINES LEFT-AREA
			west: new Ext.Panel({
				region: 'west',
				layout: 'anchor',
				autoEl: 'div',
				cls: 'x-panel-editor-west',
				collapsible: true,
				collapseTitle: ORYX.I18N.View.West,
				width: ORYX.CONFIG.PANEL_LEFT_WIDTH || 200,
				autoScroll: true,
				cmargins: { left: 0, right: 0 },
				floatable: false,
				expandTriggerAll: true,
				split: true,
				title: "West"
			}),


			// DEFINES CENTER-AREA (FOR THE EDITOR)
			center: new Ext.Panel({
				region: 'center',
				cls: 'x-panel-editor-center',
				autoScroll: true,
				items: {
					layout: "fit",
					autoHeight: true,
					el: canvasParent
				}
			})
		}

		// Hide every region except the center
		for (region in this.layout_regions) {
			if (region != "center") {
				//this.layout_regions[ region ].hide();
			}
		}

		// Config for the Ext.Viewport 
		var layout_config = {
			layout: 'border',
			items: [
				this.layout_regions.north,
				this.layout_regions.east,
				this.layout_regions.south,
				this.layout_regions.west,
				this.layout_regions.center
			]
		}

		// IF Fullscreen, use a viewport
		if (this.fullscreen) {
			this.layout = new Ext.Viewport(layout_config)

			// IF NOT, use a panel and render it to the given id
		} else {
			layout_config.renderTo = this.id;
			layout_config.height = layoutHeight;
			this.layout = new Ext.Panel(layout_config)
		}

		//Generates the ORYX-Header
		this._generateHeader();


		// Set the editor to the center, and refresh the size
		canvasParent.parentNode.setAttributeNS(null, 'align', 'center');
		canvasParent.setAttributeNS(null, 'align', 'left');
		this.getCanvas().setSize({
			width: ORYX.CONFIG.CANVAS_WIDTH,
			height: ORYX.CONFIG.CANVAS_HEIGHT
		});

	},

	_generateHeader: function () {

		var headerPanel = new Ext.Panel({
			height: 30,
			autoHeight: false,
			border: false,
			html: "<div id='oryx_editor_header'><a href=\"" + ORYX.CONFIG.WEB_URL + "\" target=\"_self\"><img src='" + ORYX.PATH + "images/oryx.small.gif' border=\"0\" /></a><div style='clear: both;'></div><div id='close_editor'></div></div>"
		});

		var maActive = ORYX.MashupAPI && ORYX.MashupAPI.isUsed;
		var maKey = maActive ? ORYX.MashupAPI.key : "";
		var maCanRun = maActive ? ORYX.MashupAPI.canRun : false;
		var maIsRemoteM = maActive ? ORYX.MashupAPI.isModelRemote : true;

		var maModelImage = maIsRemoteM ? "<img src='" + ORYX.PATH + "images/page_white_put.png'/>" : "";
		var maModelAuthI = maActive ? "<span class='mashupinfo'><img src='" + ORYX.PATH + "images/" + (maCanRun ? "plugin_error" : "plugin") + ".png'/>" + maModelImage + "</span>" : "";


		// Callback if the user changes
		var fn = function (val) {

			var publicText = ORYX.I18N.Oryx.notLoggedOn;
			var user = val && val.identifier && val.identifier != "public" ? decodeURI(val.identifier.gsub('"', "")).replace(/\+/g, " ") : "";

			if (user.length <= 0) {
				user = publicText;
			}


			var content = "<div>" +
				/*    "<div id='header_logo_image'>" +                
						"<img src='../explorer/src/img/signavio/smoky/logo2.png' border=\"0\"/>" +
					"</div>" +
					"<span class='openid " + (publicText == user ? "not" : "") + "'>" + 
					  (unescape(user)) + 
					  maModelAuthI + 
					"</span>" + 
					"<div id='header_close_image'>" +
					  "<a href=\""+ORYX.CONFIG.WEB_URL+"\" target=\"_self\" title=\"close modeler\">" +
						"<img src=getContextPath()+'/static/workflow/editor/images/close_button.png' border=\"0\" />" + 
					  "</a>" +
					"</div>" + */
				"</div>";

			if (headerPanel.body) {
				headerPanel.body.dom.innerHTML = content;
			} else {
				headerPanel.html = content
			}
		};

		ORYX.Editor.Cookie.onChange(fn);
		fn(ORYX.Editor.Cookie.getParams());

		// The oryx header
		//this.addToRegion("north", headerPanel );
	},

	/**
	 * adds a component to the specified region
	 * 
	 * @param {String} region
	 * @param {Ext.Component} component
	 * @param {String} title, optional
	 * @return {Ext.Component} dom reference to the current region or null if specified region is unknown
	 */
	addToRegion: function (region, component, title) {

		if (region.toLowerCase && this.layout_regions[region.toLowerCase()]) {
			var current_region = this.layout_regions[region.toLowerCase()];

			current_region.add(component);

			/*if( (region.toLowerCase() == 'east' || region.toLowerCase() == 'west') && current_region.items.length == 2){ //!current_region.getLayout() instanceof Ext.layout.Accordion ){
				var layout = new Ext.layout.Accordion( current_region.layoutConfig );
            	current_region.setLayout( layout );
				
				var items = current_region.items.clone();
				
				current_region.items.each(function(item){ current_region.remove( item )})
				items.each(function(item){ current_region.add( item )})
				
			}	*/

			ORYX.Log.debug("original dimensions of region %0: %1 x %2", current_region.region, current_region.width, current_region.height)

			// update dimensions of region if required.
			if (!current_region.width && component.initialConfig && component.initialConfig.width) {
				ORYX.Log.debug("resizing width of region %0: %1", current_region.region, component.initialConfig.width)
				current_region.setWidth(component.initialConfig.width)
			}
			if (component.initialConfig && component.initialConfig.height) {
				ORYX.Log.debug("resizing height of region %0: %1", current_region.region, component.initialConfig.height)
				var current_height = current_region.height || 0;
				current_region.height = component.initialConfig.height + current_height;
				current_region.setHeight(component.initialConfig.height + current_height)
			}

			// set title if provided as parameter.
			if (typeof title == "string") {
				current_region.setTitle(title);
			}

			// trigger doLayout() and show the pane
			current_region.ownerCt.doLayout();
			current_region.show();

			if (Ext.isMac)
				ORYX.Editor.resizeFix();

			return current_region;
		}

		return null;
	},
	getAvailablePlugins: function () {
		var curAvailablePlugins = ORYX.availablePlugins.clone();
		curAvailablePlugins.each(function (plugin) {
			if (this.loadedPlugins.find(function (loadedPlugin) {
				return loadedPlugin.type == this.name;
			}.bind(plugin))) {
				plugin.engaged = true;
			} else {
				plugin.engaged = false;
			}
		}.bind(this));
		return curAvailablePlugins;
	},

	loadScript: function (url, callback) {
		var script = document.createElement("script")
		script.type = "text/javascript";
		if (script.readyState) {  //IE
			script.onreadystatechange = function () {
				if (script.readyState == "loaded" || script.readyState == "complete") {
					script.onreadystatechange = null;
					callback();
				}
			};
		} else {  //Others
			script.onload = function () {
				callback();
			};
		}
		script.src = url;
		document.getElementsByTagName("head")[0].appendChild(script);
	},
	/**
	 * activate Plugin
	 * 
	 * @param {String} name
	 * @param {Function} callback
	 * 		callback(sucess, [errorCode])
	 * 			errorCodes: NOTUSEINSTENCILSET, REQUIRESTENCILSET, NOTFOUND, YETACTIVATED
	 */
	activatePluginByName: function (name, callback, loadTry) {

		var match = this.getAvailablePlugins().find(function (value) { return value.name == name });
		if (match && (!match.engaged || (match.engaged === 'false'))) {
			var loadedStencilSetsNamespaces = this.getStencilSets().keys();
			var facade = this._getPluginFacade();
			var newPlugin;
			var me = this;
			ORYX.Log.debug("初始化插件 '%0'", match.name);

			if (!match.requires || !match.requires.namespaces || match.requires.namespaces.any(function (req) { return loadedStencilSetsNamespaces.indexOf(req) >= 0 })) {
				if (!match.notUsesIn || !match.notUsesIn.namespaces || !match.notUsesIn.namespaces.any(function (req) { return loadedStencilSetsNamespaces.indexOf(req) >= 0 })) {

					try {

						var className = eval(match.name);
						var newPlugin = new className(facade, match);
						newPlugin.type = match.name;

						// If there is an GUI-Plugin, they get all Plugins-Offer-Meta-Data
						if (newPlugin.registryChanged)
							newPlugin.registryChanged(me.pluginsData);

						// If there have an onSelection-Method it will pushed to the Editor Event-Handler
						if (newPlugin.onSelectionChanged)
							me.registerOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, newPlugin.onSelectionChanged.bind(newPlugin));
						this.loadedPlugins.push(newPlugin);
						this.loadedPlugins.each(function (loaded) {
							if (loaded.registryChanged)
								loaded.registryChanged(this.pluginsData);
						}.bind(me));
						callback(true);

					} catch (e) {
						ORYX.Log.warn("Plugin %0 is not available", match.name);
						if (!!loadTry) {
							callback(false, "INITFAILED");
							return;
						}
						this.loadScript("plugins/scripts/" + match.source, this.activatePluginByName.bind(this, match.name, callback, true));
					}
				} else {
					callback(false, "NOTUSEINSTENCILSET");
					ORYX.Log.info("插件[%0]无stencilset 忽略加载'", match.name);
				}

			} else {
				callback(false, "REQUIRESTENCILSET");
				ORYX.Log.info("插件[%0]无stencilset 忽略加载", match.name);
			}


		} else {
			callback(false, match ? "NOTFOUND" : "YETACTIVATED");
			//TODO error handling
		}
	},

	/**
	 * 触发按钮事件
	 */
	raiseToolButtonAction: function (btnName, completeCallback) {
		var toolbuttons = this.loadedPlugins[1].buttons;
		for (i = 0; i < toolbuttons.length; i++) {
			if (toolbuttons[i].name === btnName) {
				//注意插件定义之前已经bind了第一参数这里.这里少一个参数.
				return toolbuttons[i].functionality(undefined, completeCallback);
			}
		}
	},

	/**
	 *  Laden der Plugins
	 */
	loadPlugins: function () {

		// if there should be plugins but still are none, try again.
		// TODO this should wait for every plugin respectively.
		/*if (!ORYX.Plugins && ORYX.availablePlugins.length > 0) {
			window.setTimeout(this.loadPlugins.bind(this), 100);
			return;
		}*/

		var me = this;
		var newPlugins = [];


		var loadedStencilSetsNamespaces = this.getStencilSets().keys();

		// Available Plugins will be initalize
		var facade = this._getPluginFacade();

		// If there is an Array where all plugins are described, than only take those
		// (that comes from the usage of oryx with a mashup api)
		if (ORYX.MashupAPI && ORYX.MashupAPI.loadablePlugins && ORYX.MashupAPI.loadablePlugins instanceof Array) {

			// Get the plugins from the available plugins (those who are in the plugins.xml)
			ORYX.availablePlugins = $A(ORYX.availablePlugins).findAll(function (value) {
				return ORYX.MashupAPI.loadablePlugins.include(value.name)
			})

			// Add those plugins to the list, which are only in the loadablePlugins list
			ORYX.MashupAPI.loadablePlugins.each(function (className) {
				if (!(ORYX.availablePlugins.find(function (val) { return val.name == className }))) {
					ORYX.availablePlugins.push({ name: className });
				}
			})
		}


		ORYX.availablePlugins.each(function (value) {
			ORYX.Log.debug("初始化插件 '%0'", value.name);
			if ((!value.requires || !value.requires.namespaces || value.requires.namespaces.any(function (req) { return loadedStencilSetsNamespaces.indexOf(req) >= 0 })) &&
				(!value.notUsesIn || !value.notUsesIn.namespaces || !value.notUsesIn.namespaces.any(function (req) { return loadedStencilSetsNamespaces.indexOf(req) >= 0 })) &&
				/*only load activated plugins or undefined */
				(value.engaged || (value.engaged === undefined))) {


				var className = eval(value.name);
				if (className) {
					var plugin = new className(facade, value);
					plugin.type = value.name;
					newPlugins.push(plugin);
					plugin.engaged = true;
				}

			} else {
				ORYX.Log.info("插件[%0]需要stencilset 忽略加载", value.name);
			}

		});

		newPlugins.each(function (value) {
			// If there is an GUI-Plugin, they get all Plugins-Offer-Meta-Data
			if (value.registryChanged)
				value.registryChanged(me.pluginsData);

			// If there have an onSelection-Method it will pushed to the Editor Event-Handler
			if (value.onSelectionChanged)
				me.registerOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, value.onSelectionChanged.bind(value));
		});

		this.loadedPlugins = newPlugins;

		// Hack for the Scrollbars
		if (Ext.isMac) {
			ORYX.Editor.resizeFix();
		}

		this.registerPluginsOnKeyEvents();

		this.setSelection();

	},

	/**
	 * Creates the Canvas
	 * @param {String} [stencilType] The stencil type used for creating the canvas. If not given, a stencil with myBeRoot = true from current stencil set is taken.
	 * @param {Object} [canvasConfig] Any canvas properties (like language).
	 */
	_createCanvas: function (stencilType, canvasConfig) {
		if (stencilType) {
			// Add namespace to stencilType
			if (stencilType.search(/^http/) === -1) {
				stencilType = this.getStencilSets().values()[0].namespace() + stencilType;
			}
		}
		else {
			// Get any root stencil type
			stencilType = this.getStencilSets().values()[0].findRootStencilName();
		}

		// get the stencil associated with the type
		var canvasStencil = ORYX.Core.StencilSet.stencil(stencilType);

		if (!canvasStencil)
			ORYX.Log.fatal("初始化失败, because the stencil with the type %0 is not part of one of the loaded stencil sets.", stencilType);

		// create all dom
		// TODO fix border, so the visible canvas has a double border and some spacing to the scrollbars
		var div = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", null, ['div']);
		// set class for custom styling
		div.addClassName("ORYX_Editor");

		// create the canvas
		this._canvas = new ORYX.Core.Canvas({
			width: ORYX.CONFIG.CANVAS_WIDTH,
			height: ORYX.CONFIG.CANVAS_HEIGHT,
			'eventHandlerCallback': this.handleEvents.bind(this),
			id: this.id,
			parentNode: div
		}, canvasStencil);

		if (canvasConfig) {
			// Migrate canvasConfig to an RDF-like structure
			//FIXME this isn't nice at all because we don't want rdf any longer
			var properties = [];
			for (field in canvasConfig) {
				properties.push({
					prefix: 'oryx',
					name: field,
					value: canvasConfig[field]
				});
			}

			this._canvas.deserialize(properties);
		}

	},

	/**
	 * Returns a per-editor singleton plugin facade.
	 * To be used in plugin initialization.
	 * 获取插件的外部接口facade
	 */
	_getPluginFacade: function () {

		// if there is no pluginfacade already created:
		if (!(this._pluginFacade))

			// create it.
			this._pluginFacade = {

				activatePluginByName: this.activatePluginByName.bind(this),
				//deactivatePluginByName:		this.deactivatePluginByName.bind(this),
				getAvailablePlugins: this.getAvailablePlugins.bind(this),
				offer: this.offer.bind(this),
				getStencilSets: this.getStencilSets.bind(this),
				getStencilSetExtensionDefinition: function () { return Object.clone(this.ss_extensions_def || {}) }.bind(this),
				getRules: this.getRules.bind(this),
				loadStencilSet: this.loadStencilSet.bind(this),
				createShape: this.createShape.bind(this),
				deleteShape: this.deleteShape.bind(this),
				getSelection: this.getSelection.bind(this),
				setSelection: this.setSelection.bind(this),
				updateSelection: this.updateSelection.bind(this),
				getCanvas: this.getCanvas.bind(this),

				importJSON: this.importJSON.bind(this),
				importERDF: this.importERDF.bind(this),
				getERDF: this.getERDF.bind(this),
				getJSON: this.getJSON.bind(this),
				getSerializedJSON: this.getSerializedJSON.bind(this),

				executeCommands: this.executeCommands.bind(this),
				isExecutingCommands: this.isExecutingCommands.bind(this),

				registerOnEvent: this.registerOnEvent.bind(this),
				unregisterOnEvent: this.unregisterOnEvent.bind(this),
				raiseEvent: this.handleEvents.bind(this),
				enableEvent: this.enableEvent.bind(this),
				disableEvent: this.disableEvent.bind(this),

				eventCoordinates: this.eventCoordinates.bind(this),
				addToRegion: this.addToRegion.bind(this),

				getModelMetaData: this.getModelMetaData.bind(this)
			};

		// return it.
		return this._pluginFacade;
	},

	isExecutingCommands: function () {
		return !!this.commandExecuting;
	},

	/**
	 * Implementes the command pattern
	 * (The real usage of the command pattern
	 * is implemented and shown in the Plugins/undo.js)
	 *
	 * @param <Oryx.Core.Command>[] Array of commands
	 */
	executeCommands: function (commands) {

		if (!this.commandStack) {
			this.commandStack = [];
		}
		if (!this.commandStackExecuted) {
			this.commandStackExecuted = [];
		}


		this.commandStack = [].concat(this.commandStack)
			.concat(commands);

		// Check if already executes
		if (this.commandExecuting) { return; }

		// Start execution
		this.commandExecuting = true;

		// Iterate over all commands
		while (this.commandStack.length > 0) {
			var command = this.commandStack.shift();
			// and execute it
			command.execute();
			this.commandStackExecuted.push(command);
		}

		// Raise event for executing commands
		this.handleEvents({
			type: ORYX.CONFIG.EVENT_EXECUTE_COMMANDS,
			commands: this.commandStackExecuted
		});

		// Remove temporary vars
		delete this.commandStack;
		delete this.commandStackExecuted;
		delete this.commandExecuting;


		this.updateSelection();

	},

    /**
     * Returns JSON of underlying canvas (calls ORYX.Canvas#toJSON()).
     * @return {Object} Returns JSON representation as JSON object.
     */
	getJSON: function () {
		var canvas = this.getCanvas().toJSON();
		canvas.ssextensions = this.getStencilSets().values()[0].extensions().keys().findAll(function (sse) { return !sse.endsWith('/meta#') });
		return canvas;
	},

    /**
     * Serializes a call to toJSON().
     * @return {String} Returns JSON representation as string.
     */
	getSerializedJSON: function () {
		return Ext.encode(this.getJSON());
	},

    /**
	 * @return {String} Returns eRDF representation.
	 * @deprecated Use ORYX.Editor#getJSON instead, if possible.
	 */
	getERDF: function () {

		// Get the serialized dom
		var serializedDOM = DataManager.serializeDOM(this._getPluginFacade());

		// Add xml definition if there is no
		serializedDOM = '<?xml version="1.0" encoding="utf-8"?>' +
			'<html xmlns="http://www.w3.org/1999/xhtml" ' +
			'xmlns:b3mn="http://b3mn.org/2007/b3mn" ' +
			'xmlns:ext="http://b3mn.org/2007/ext" ' +
			'xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ' +
			'xmlns:atom="http://b3mn.org/2007/atom+xhtml">' +
			'<head profile="http://purl.org/NET/erdf/profile">' +
			'<link rel="schema.dc" href="http://purl.org/dc/elements/1.1/" />' +
			'<link rel="schema.dcTerms" href="http://purl.org/dc/terms/ " />' +
			'<link rel="schema.b3mn" href="http://b3mn.org" />' +
			'<link rel="schema.oryx" href="http://oryx-editor.org/" />' +
			'<link rel="schema.raziel" href="http://raziel.org/" />' +
			'<base href="' +
			location.href.split("?")[0] +
			'" />' +
			'</head><body>' +
			serializedDOM +
			'</body></html>';

		return serializedDOM;
	},

	/**
	* Imports shapes in JSON as expected by {@link ORYX.Editor#loadSerialized}
	* @param {Object|String} jsonObject The (serialized) json object to be imported
	* @param {boolean } [noSelectionAfterImport=false] Set to true if no shapes should be selected after import
	* @throws {SyntaxError} If the serialized json object contains syntax errors
	*/
	importJSON: function (jsonObject, noSelectionAfterImport) {

		try {
			jsonObject = this.renewResourceIds(jsonObject);
		} catch (error) {
			throw error;
		}
		//check, if the imported json model can be loaded in this editor
		// (stencil set has to fit)
		if (jsonObject.stencilset.namespace && jsonObject.stencilset.namespace !== this.getCanvas().getStencil().stencilSet().namespace()) {
			Ext.Msg.alert(ORYX.I18N.JSONImport.title, String.format(ORYX.I18N.JSONImport.wrongSS, jsonObject.stencilset.namespace, this.getCanvas().getStencil().stencilSet().namespace()));
			return null;
		} else {
			var commandClass = ORYX.Core.Command.extend({
				construct: function (jsonObject, loadSerializedCB, noSelectionAfterImport, facade) {
					this.jsonObject = jsonObject;
					this.noSelection = noSelectionAfterImport;
					this.facade = facade;
					this.shapes;
					this.connections = [];
					this.parents = new Hash();
					this.selection = this.facade.getSelection();
					this.loadSerialized = loadSerializedCB;
				},
				execute: function () {

					if (!this.shapes) {
						// Import the shapes out of the serialization		
						this.shapes = this.loadSerialized(this.jsonObject);

						//store all connections
						this.shapes.each(function (shape) {

							if (shape.getDockers) {
								var dockers = shape.getDockers();
								if (dockers) {
									if (dockers.length > 0) {
										this.connections.push([dockers.first(), dockers.first().getDockedShape(), dockers.first().referencePoint]);
									}
									if (dockers.length > 1) {
										this.connections.push([dockers.last(), dockers.last().getDockedShape(), dockers.last().referencePoint]);
									}
								}
							}

							//store parents
							this.parents[shape.id] = shape.parent;
						}.bind(this));
					} else {
						this.shapes.each(function (shape) {
							this.parents[shape.id].add(shape);
						}.bind(this));

						this.connections.each(function (con) {
							con[0].setDockedShape(con[1]);
							con[0].setReferencePoint(con[2]);
							con[0].update();
						});
					}

					//this.parents.values().uniq().invoke("update");
					this.facade.getCanvas().update();

					if (!this.noSelection)
						this.facade.setSelection(this.shapes);
					else
						this.facade.updateSelection();

					// call updateSize again, because during loadSerialized the edges' bounds  
					// are not yet initialized properly
					this.facade.getCanvas().updateSize();

				},
				rollback: function () {
					var selection = this.facade.getSelection();

					this.shapes.each(function (shape) {
						selection = selection.without(shape);
						this.facade.deleteShape(shape);
					}.bind(this));

					/*this.parents.values().uniq().each(function(parent) {
						if(!this.shapes.member(parent))
							parent.update();
					}.bind(this));*/

					this.facade.getCanvas().update();

					this.facade.setSelection(selection);
				}
			})

			var command = new commandClass(jsonObject,
				this.loadSerialized.bind(this),
				noSelectionAfterImport,
				this._getPluginFacade());

			this.executeCommands([command]);

			return command.shapes.clone();
		}
	},

    /**
     * This method renew all resource Ids and according references.
     * Warning: The implementation performs a substitution on the serialized object for
     * easier implementation. This results in a low performance which is acceptable if this
     * is only used when importing models.
     * @param {Object|String} jsonObject
     * @throws {SyntaxError} If the serialized json object contains syntax errors.
     * @return {Object} The jsonObject with renewed ids.
     * @private
     */
	renewResourceIds: function (jsonObject) {
		// For renewing resource ids, a serialized and object version is needed
		if (Ext.type(jsonObject) === "string") {
			try {
				var serJsonObject = jsonObject;
				jsonObject = Ext.decode(jsonObject);
			} catch (error) {
				throw new SyntaxError(error.message);
			}
		} else {
			var serJsonObject = Ext.encode(jsonObject);
		}

		// collect all resourceIds recursively
		var collectResourceIds = function (shapes) {
			if (!shapes) return [];

			return shapes.map(function (shape) {
				return collectResourceIds(shape.childShapes).concat(shape.resourceId);
			}).flatten();
		}
		var resourceIds = collectResourceIds(jsonObject.childShapes);

		// Replace each resource id by a new one
		resourceIds.each(function (oldResourceId) {
			var newResourceId = ORYX.Editor.provideId();
			serJsonObject = serJsonObject.gsub('"' + oldResourceId + '"', '"' + newResourceId + '"')
		});

		return Ext.decode(serJsonObject);
	},

	/**
	 * Import erdf structure to the editor
	 *
	 */
	importERDF: function (erdfDOM) {

		var serialized = this.parseToSerializeObjects(erdfDOM);

		if (serialized)
			return this.importJSON(serialized, true);
	},

	/**
	 * Parses one model (eRDF) to the serialized form (JSON)
	 * 
	 * @param {Object} oneProcessData
	 * @return {Object} The JSON form of given eRDF model, or null if it couldn't be extracted 
	 */
	parseToSerializeObjects: function (oneProcessData) {

		// Firefox splits a long text node into chunks of 4096 characters.
		// To prevent truncation of long property values the normalize method must be called
		if (oneProcessData.normalize) oneProcessData.normalize();
		try {
			var xsl = "";
			var source = ORYX.PATH + "/lib/extract-rdf.xsl";
			new Ajax.Request(source, {
				asynchronous: false,
				method: 'get',
				onSuccess: function (transport) {
					xsl = transport.responseText
				}.bind(this),
				onFailure: (function (transport) {
					ORYX.Log.error("XSL load failed" + transport);
				}).bind(this)
			});
			var domParser = new DOMParser();
			var xmlObject = oneProcessData;
			var xslObject = domParser.parseFromString(xsl, "text/xml");
			var xsltProcessor = new XSLTProcessor();
			var xslRef = document.implementation.createDocument("", "", null);
			xsltProcessor.importStylesheet(xslObject);

			var new_rdf = xsltProcessor.transformToFragment(xmlObject, document);
			var serialized_rdf = (new XMLSerializer()).serializeToString(new_rdf);
		} catch (e) {
			Ext.Msg.alert("Oryx", error);
			var serialized_rdf = "";
		}

		// Firefox 2 to 3 problem?!
		serialized_rdf = !serialized_rdf.startsWith("<?xml") ? "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + serialized_rdf : serialized_rdf;

		var req = new Ajax.Request(ORYX.CONFIG.ROOT_PATH + "/rdf2json", {
			method: 'POST',
			asynchronous: false,
			onSuccess: function (transport) {
				Ext.decode(transport.responseText);
			},
			parameters: {
				rdf: serialized_rdf
			}
		});

		return Ext.decode(req.transport.responseText);
	},

    /**
     * Loads serialized model to the oryx.
     * @example
     * editor.loadSerialized({
     *    resourceId: "mymodel1",
     *    childShapes: [
     *       {
     *          stencil:{ id:"Subprocess" },
     *          outgoing:[{resourceId: 'aShape'}],
     *          target: {resourceId: 'aShape'},
     *          bounds:{ lowerRight:{ y:510, x:633 }, upperLeft:{ y:146, x:210 } },
     *          resourceId: "myshape1",
     *          childShapes:[],
     *          properties:{},
     *       }
     *    ],
     *    properties:{
     *       language: "English"
     *    },
     *    stencilset:{
     *       url:"http://localhost:8080/oryx/stencilsets/bpmn1.1/bpmn1.1.json"
     *    },
     *    stencil:{
     *       id:"BPMNDiagram"
     *    }
     * });
     * @param {Object} model Description of the model to load.
     * @param {Array} [model.ssextensions] List of stenctil set extensions.
     * @param {String} model.stencilset.url
     * @param {String} model.stencil.id 
     * @param {Array} model.childShapes
     * @param {Array} [model.properties]
     * @param {String} model.resourceId
     * @return {ORYX.Core.Shape[]} List of created shapes
     * @methodOf ORYX.Editor.prototype
     */
	loadSerialized: function (model, requestMeta) {
		var canvas = this.getCanvas();

		// Bugfix (cf. http://code.google.com/p/oryx-editor/issues/detail?id=240)
		// Deserialize the canvas' stencil set extensions properties first!
		this.loadSSExtensions(model.ssextensions);

		// Load Meta Data Extension if available
		// #Signavio
		if (requestMeta === true) {
			var metaDataExtension = this.getExtensionForMetaData();
			if (metaDataExtension) {
				this.loadSSExtension(metaDataExtension);
			}
		}

		var shapes = this.getCanvas().addShapeObjects(model.childShapes, this.handleEvents.bind(this));

		if (model.properties) {
			for (key in model.properties) {
				var value = model.properties[key];
				var prop = this.getCanvas().getStencil().property("oryx-" + key);
				if (!(typeof value === "string") && (!prop || !prop.isList())) {
					value = Ext.encode(value);
				}
				this.getCanvas().setProperty("oryx-" + key, value);
			}
		}


		this.getCanvas().updateSize();

		// Force to update the selection
		this.selection = [null];
		this.setSelection([]);

		return shapes;
	},

	/**
	 * Return the namespace of the extension which
	 * provided all the self defined meta data
	 * @return {String} Returns null if no extension is defined, otherwise the namespace
	 *
	 */
	getExtensionForMetaData: function () {
		if (!this.ss_extensions_def || !(this.ss_extensions_def.extensions instanceof Array)) {
			return null;
		}

		var stencilsets = this.getStencilSets();
		var extension = this.ss_extensions_def.extensions.find(function (ex) {
			return !!stencilsets[ex["extends"]] && ex.namespace.endsWith("/meta#");
		});

		return extension ? extension.namespace || null : null;
	},

    /**
     * Calls ORYX.Editor.prototype.ss_extension_namespace for each element
     * @param {Array} ss_extension_namespaces An array of stencil set extension namespaces.
     */
	loadSSExtensions: function (ss_extension_namespaces) {
		if (!ss_extension_namespaces) return;

		ss_extension_namespaces.each(function (ss_extension_namespace) {
			this.loadSSExtension(ss_extension_namespace);
		}.bind(this));
	},

	/**
	* Loads a stencil set extension.
	* The stencil set extensions definiton file must already
	* be loaded when the editor is initialized.
	*/
	loadSSExtension: function (ss_extension_namespace) {

		if (this.ss_extensions_def) {
			var extension = this.ss_extensions_def.extensions.find(function (ex) {
				return (ex.namespace == ss_extension_namespace);
			});

			if (!extension) {
				return;
			}

			var stencilset = this.getStencilSets()[extension["extends"]];

			if (!stencilset) {
				return;
			}

			// Check if absolute or relative url
			if ((extension["definition"] || "").startsWith("/")) {
				stencilset.addExtension(extension["definition"])
			} else {
				stencilset.addExtension(ORYX.CONFIG.SS_EXTENSIONS_FOLDER + extension["definition"])
			}

			//stencilset.addExtension("/oryx/build/stencilsets/extensions/" + extension["definition"])
			this.getRules().initializeRules(stencilset);

			this._getPluginFacade().raiseEvent({
				type: ORYX.CONFIG.EVENT_STENCIL_SET_LOADED
			});
		}

	},

	disableEvent: function (eventType) {
		if (eventType == ORYX.CONFIG.EVENT_KEYDOWN) {
			this._keydownEnabled = false;
		}
		if (eventType == ORYX.CONFIG.EVENT_KEYUP) {
			this._keyupEnabled = false;
		}
		if (this.DOMEventListeners.keys().member(eventType)) {
			var value = this.DOMEventListeners.remove(eventType);
			this.DOMEventListeners['disable_' + eventType] = value;
		}
	},

	enableEvent: function (eventType) {
		if (eventType == ORYX.CONFIG.EVENT_KEYDOWN) {
			this._keydownEnabled = true;
		}

		if (eventType == ORYX.CONFIG.EVENT_KEYUP) {
			this._keyupEnabled = true;
		}

		if (this.DOMEventListeners.keys().member("disable_" + eventType)) {
			var value = this.DOMEventListeners.remove("disable_" + eventType);
			this.DOMEventListeners[eventType] = value;
		}
	},

	/**
	 *  Methods for the PluginFacade
	 */
	registerOnEvent: function (eventType, callback) {
		if (!(this.DOMEventListeners.keys().member(eventType))) {
			this.DOMEventListeners[eventType] = [];
		}

		this.DOMEventListeners[eventType].push(callback);
	},

	unregisterOnEvent: function (eventType, callback) {
		if (this.DOMEventListeners.keys().member(eventType)) {
			this.DOMEventListeners[eventType] = this.DOMEventListeners[eventType].without(callback);
		} else {
			// Event is not supported
			// TODO: Error Handling
		}
	},
	/**
	 * 获取选择的图像
	 */
	getSelection: function () {
		return this.selection || [];
	},

	getStencilSets: function () {
		return ORYX.Core.StencilSet.stencilSets(this.id);
	},

	getRules: function () {
		return ORYX.Core.StencilSet.rules(this.id);
	},

	loadStencilSet: function (source) {
		try {
			ORYX.Core.StencilSet.loadStencilSet(source, this.id);
			this.handleEvents({ type: ORYX.CONFIG.EVENT_STENCIL_SET_LOADED });
		} catch (e) {
			ORYX.Log.warn("Requesting stencil set file failed. (" + e + ")");
		}
	},

	offer: function (pluginData) {
		if (!this.pluginsData.member(pluginData)) {
			this.pluginsData.push(pluginData);
		}
	},

	/**
	 * It creates an new event or adds the callback, if already existing,
	 * for the key combination that the plugin passes in keyCodes attribute
	 * of the offer method.
	 * 
	 * The new key down event fits the schema:
	 * 		key.event[.metactrl][.alt][.shift].'thekeyCode'
	 */
	registerPluginsOnKeyEvents: function () {
		this.pluginsData.each(function (pluginData) {

			if (pluginData.keyCodes) {

				pluginData.keyCodes.each(function (keyComb) {
					var eventName = "key.event";

					/* Include key action */
					eventName += '.' + keyComb.keyAction;

					if (keyComb.metaKeys) {
						/* Register on ctrl or apple meta key as meta key */
						if (keyComb.metaKeys.
							indexOf(ORYX.CONFIG.META_KEY_META_CTRL) > -1) {
							eventName += "." + ORYX.CONFIG.META_KEY_META_CTRL;
						}

						/* Register on alt key as meta key */
						if (keyComb.metaKeys.
							indexOf(ORYX.CONFIG.META_KEY_ALT) > -1) {
							eventName += '.' + ORYX.CONFIG.META_KEY_ALT;
						}

						/* Register on shift key as meta key */
						if (keyComb.metaKeys.
							indexOf(ORYX.CONFIG.META_KEY_SHIFT) > -1) {
							eventName += '.' + ORYX.CONFIG.META_KEY_SHIFT;
						}
					}

					/* Register on the actual key */
					if (keyComb.keyCode) {
						eventName += '.' + keyComb.keyCode;
					}

					/* Register the event */
					ORYX.Log.debug("Register Plugin on Key Event: %0", eventName);
					if (pluginData.toggle === true && pluginData.buttonInstance) {
						this.registerOnEvent(eventName, function () {
							pluginData.buttonInstance.toggle(!pluginData.buttonInstance.pressed); // Toggle 
							pluginData.functionality.call(pluginData, pluginData.buttonInstance, pluginData.buttonInstance.pressed); // Call function
						});
					} else {
						this.registerOnEvent(eventName, pluginData.functionality)
					}

				}.bind(this));
			}
		}.bind(this));
	},
	/**
	 * 对象或数组是否相等
	 */
	isEqual: function (a, b) {
		return a === b || (a.length === b.length && a.all(function (r) { return b.include(r) }))
	},

	isDirty: function (a) {
		return a.any(function (shape) { return shape.isPropertyChanged() })
	},
	/**
	 * 设置选择的图像,触发选择改变事件
	 */
	setSelection: function (elements, subSelectionElement, force) {

		if (!elements) { elements = []; }
		if (!(elements instanceof Array)) { elements = [elements]; }

		elements = elements.findAll(function (n) { return n && n instanceof ORYX.Core.Shape });

		if (elements[0] instanceof ORYX.Core.Canvas) {
			elements = [];
		}

		if (!force && this.isEqual(this.selection, elements) && !this.isDirty(elements)) {
			return;
		}

		this.selection = elements;
		this._subSelection = subSelectionElement;

		this.handleEvents({ type: ORYX.CONFIG.EVENT_SELECTION_CHANGED, elements: elements, subSelection: subSelectionElement, force: !!force })
	},
	/**
	 * 更新选择的图像
	 */
	updateSelection: function () {
		this.setSelection(this.selection, this._subSelection, true);
		/*var s = this.selection;
		this.setSelection();
		this.setSelection(s);*/
	},
	/**
	 * 获取画布
	 */
	getCanvas: function () {
		return this._canvas;
	},


	/**
	*	option = {
	*		type: string,
	*		position: {x:int, y:int},
	*		connectingType:	uiObj-Class
	*		connectedShape: uiObj
	*		draggin: bool
	*		namespace: url
	*       parent: ORYX.Core.AbstractShape
	*		template: a template shape that the newly created inherits properties from.
	*		}
	*/
	createShape: function (option) {

		if (option && option.serialize && option.serialize instanceof Array) {

			var type = option.serialize.find(function (obj) { return (obj.prefix + "-" + obj.name) == "oryx-type" });
			var stencil = ORYX.Core.StencilSet.stencil(type.value);

			if (stencil.type() == 'node') {
				var newShapeObject = new ORYX.Core.Node({ 'eventHandlerCallback': this.handleEvents.bind(this) }, stencil);
			} else {
				var newShapeObject = new ORYX.Core.Edge({ 'eventHandlerCallback': this.handleEvents.bind(this) }, stencil);
			}

			this.getCanvas().add(newShapeObject);
			newShapeObject.deserialize(option.serialize);

			return newShapeObject;
		}

		// If there is no argument, throw an exception
		if (!option || !option.type || !option.namespace) { throw "To create a new shape you have to give an argument with type and namespace"; }

		var canvas = this.getCanvas();
		var newShapeObject;

		// Get the shape type
		var shapetype = option.type;

		// Get the stencil set
		var sset = ORYX.Core.StencilSet.stencilSet(option.namespace);

		// Create an New Shape, dependents on an Edge or a Node
		if (sset.stencil(shapetype).type() == "node") {
			newShapeObject = new ORYX.Core.Node({ 'eventHandlerCallback': this.handleEvents.bind(this) }, sset.stencil(shapetype))
		} else {
			newShapeObject = new ORYX.Core.Edge({ 'eventHandlerCallback': this.handleEvents.bind(this) }, sset.stencil(shapetype))
		}

		// when there is a template, inherit the properties.
		if (option.template) {

			newShapeObject._jsonStencil.properties = option.template._jsonStencil.properties;
			newShapeObject.postProcessProperties();
		}

		// Add to the canvas
		if (option.parent && newShapeObject instanceof ORYX.Core.Node) {
			option.parent.add(newShapeObject);
		} else {
			canvas.add(newShapeObject);
		}


		// Set the position
		var point = option.position ? option.position : { x: 100, y: 200 };


		var con;
		// If there is create a shape and in the argument there is given an ConnectingType and is instance of an edge
		if (option.connectingType && option.connectedShape && !(newShapeObject instanceof ORYX.Core.Edge)) {

			// there will be create a new Edge
			con = new ORYX.Core.Edge({ 'eventHandlerCallback': this.handleEvents.bind(this) }, sset.stencil(option.connectingType));

			// And both endings dockers will be referenced to the both shapes
			con.dockers.first().setDockedShape(option.connectedShape);

			var magnet = option.connectedShape.getDefaultMagnet()
			var cPoint = magnet ? magnet.bounds.center() : option.connectedShape.bounds.midPoint();
			con.dockers.first().setReferencePoint(cPoint);
			con.dockers.last().setDockedShape(newShapeObject);
			con.dockers.last().setReferencePoint(newShapeObject.getDefaultMagnet().bounds.center());

			// The Edge will be added to the canvas and be updated
			canvas.add(con);
			//con.update();

		}

		// Move the new Shape to the position
		if (newShapeObject instanceof ORYX.Core.Edge && option.connectedShape) {

			newShapeObject.dockers.first().setDockedShape(option.connectedShape);

			if (option.connectedShape instanceof ORYX.Core.Node) {
				newShapeObject.dockers.first().setReferencePoint(option.connectedShape.getDefaultMagnet().bounds.center());
				newShapeObject.dockers.last().bounds.centerMoveTo(point);
			} else {
				newShapeObject.dockers.first().setReferencePoint(option.connectedShape.bounds.midPoint());
			}

		} else {

			var b = newShapeObject.bounds
			if (newShapeObject instanceof ORYX.Core.Node && newShapeObject.dockers.length == 1) {
				b = newShapeObject.dockers.first().bounds
			}

			b.centerMoveTo(point);

			var upL = b.upperLeft();
			b.moveBy(-Math.min(upL.x, 0), -Math.min(upL.y, 0))

			var lwR = b.lowerRight();
			b.moveBy(-Math.max(lwR.x - canvas.bounds.width(), 0), -Math.max(lwR.y - canvas.bounds.height(), 0))

		}

		// Update the shape
		if (newShapeObject instanceof ORYX.Core.Edge) {
			newShapeObject._update(false);
		}

		// And refresh the selection
		if (!(newShapeObject instanceof ORYX.Core.Edge) && !(option.dontUpdateSelection)) {
			this.setSelection([newShapeObject]);
		}

		if (con && con.alignDockers) {
			con.alignDockers();
		}
		if (newShapeObject.alignDockers) {
			newShapeObject.alignDockers();
		}

		return newShapeObject;
	},

	deleteShape: function (shape) {

		if (!shape || !shape.parent) { return }

		//remove shape from parent
		// this also removes it from DOM
		shape.parent.remove(shape);

		//delete references to outgoing edges
		shape.getOutgoingShapes().each(function (os) {
			var docker = os.getDockers().first();
			if (docker && docker.getDockedShape() == shape) {
				docker.setDockedShape(undefined);
			}
		});

		//delete references to incoming edges
		shape.getIncomingShapes().each(function (is) {
			var docker = is.getDockers().last();
			if (docker && docker.getDockedShape() == shape) {
				docker.setDockedShape(undefined);
			}
		});

		//delete references of the shape's dockers
		shape.getDockers().each(function (docker) {
			docker.setDockedShape(undefined);
		});
	},

	/**
	 * Returns an object with meta data about the model.
	 * Like name, description, ...
	 * 
	 * Empty object with the current backend.
	 * 
	 * @return {Object} Meta data about the model
	 */
	getModelMetaData: function () {
		return this.modelMetaData;
	},

	/* Event-Handler Methods */

	/**
	* Helper method to execute an event immediately. The event is not
	* scheduled in the _eventsQueue. Needed to handle Layout-Callbacks.
	*/
	_executeEventImmediately: function (eventObj) {
		if (this.DOMEventListeners.keys().member(eventObj.event.type)) {
			this.DOMEventListeners[eventObj.event.type].each((function (value) {
				value(eventObj.event, eventObj.arg);
			}).bind(this));
		}
	},

	_executeEvents: function () {
		this._queueRunning = true;
		while (this._eventsQueue.length > 0) {
			var val = this._eventsQueue.shift();
			this._executeEventImmediately(val);
		}
		this._queueRunning = false;
	},

	/**
	 * Leitet die Events an die Editor-Spezifischen Event-Methoden weiter
	 * @param {Object} event Event , welches gefeuert wurde
	 * @param {Object} uiObj Target-UiObj
	 */
	handleEvents: function (event, uiObj) {

		ORYX.Log.trace("Dispatching event type %0 on %1", event.type, uiObj);

		switch (event.type) {
			case ORYX.CONFIG.EVENT_MOUSEDOWN:
				this._handleMouseDown(event, uiObj);
				break;
			case ORYX.CONFIG.EVENT_MOUSEMOVE:
				this._handleMouseMove(event, uiObj);
				break;
			case ORYX.CONFIG.EVENT_MOUSEUP:
				this._handleMouseUp(event, uiObj);
				break;
			case ORYX.CONFIG.EVENT_MOUSEOVER:
				this._handleMouseHover(event, uiObj);
				break;
			case ORYX.CONFIG.EVENT_MOUSEOUT:
				this._handleMouseOut(event, uiObj);
				break;
		}
		/* Force execution if necessary. Used while handle Layout-Callbacks. */
		if (event.forceExecution) {
			this._executeEventImmediately({ event: event, arg: uiObj });
		} else {
			this._eventsQueue.push({ event: event, arg: uiObj });
		}

		if (!this._queueRunning) {
			this._executeEvents();
		}

		// TODO: Make this return whether no listener returned false.
		// So that, when one considers bubbling undesireable, it won't happen.
		return false;
	},

	isValidEvent: function (e) {
		try {
			var isInput = ["INPUT", "TEXTAREA"].include(e.target.tagName.toUpperCase());
			var gridHasFocus = e.target.className.include("x-grid3-focus") && !e.target.className.include("x-grid3-focus-canvas");
			return !isInput && !gridHasFocus;
		} catch (e) {
			return false;
		}
	},

	catchKeyUpEvents: function (event) {
		if (!this._keyupEnabled) {
			return;
		}
		/* assure we have the current event. */
		if (!event)
			event = window.event;

		// Checks if the event comes from some input field
		if (!this.isValidEvent(event)) {
			return;
		}

		/* Create key up event type */
		var keyUpEvent = this.createKeyCombEvent(event, ORYX.CONFIG.KEY_ACTION_UP);

		ORYX.Log.debug("Key Event to handle: %0", keyUpEvent);

		/* forward to dispatching. */
		this.handleEvents({ type: keyUpEvent, event: event });
	},

	/**
	 * Catches all key down events and forward the appropriated event to 
	 * dispatching concerning to the pressed keys.
	 * 
	 * @param {Event} 
	 * 		The key down event to handle
	 */
	catchKeyDownEvents: function (event) {
		if (!this._keydownEnabled) {
			return;
		}
		/* Assure we have the current event. */
		if (!event)
			event = window.event;

		/* Fixed in FF3 */
		// This is a mac-specific fix. The mozilla event object has no knowledge
		// of meta key modifier on osx, however, it is needed for certain
		// shortcuts. This fix adds the metaKey field to the event object, so
		// that all listeners that registered per Oryx plugin facade profit from
		// this. The original bug is filed in
		// https://bugzilla.mozilla.org/show_bug.cgi?id=418334
		//if (this.__currentKey == ORYX.CONFIG.KEY_CODE_META) {
		//	event.appleMetaKey = true;
		//}
		//this.__currentKey = pressedKey;

		// Checks if the event comes from some input field
		if (!this.isValidEvent(event)) {
			return;
		}

		/* Create key up event type */
		var keyDownEvent = this.createKeyCombEvent(event, ORYX.CONFIG.KEY_ACTION_DOWN);

		ORYX.Log.debug("Key Event to handle: %0", keyDownEvent);

		/* Forward to dispatching. */
		this.handleEvents({ type: keyDownEvent, event: event });
	},

	/**
	 * Creates the event type name concerning to the pressed keys.
	 * 
	 * @param {Event} keyDownEvent
	 * 		The source keyDownEvent to build up the event name
	 */
	createKeyCombEvent: function (keyEvent, keyAction) {

		/* Get the currently pressed key code. */
		var pressedKey = keyEvent.which || keyEvent.keyCode;
		//this.__currentKey = pressedKey;

		/* Event name */
		var eventName = "key.event";

		/* Key action */
		if (keyAction) {
			eventName += "." + keyAction;
		}

		/* Ctrl or apple meta key is pressed */
		if (keyEvent.ctrlKey || keyEvent.metaKey) {
			eventName += "." + ORYX.CONFIG.META_KEY_META_CTRL;
		}

		/* Alt key is pressed */
		if (keyEvent.altKey) {
			eventName += "." + ORYX.CONFIG.META_KEY_ALT;
		}

		/* Alt key is pressed */
		if (keyEvent.shiftKey) {
			eventName += "." + ORYX.CONFIG.META_KEY_SHIFT;
		}

		/* Return the composed event name */
		return eventName + "." + pressedKey;
	},


	_handleMouseDown: function (event, uiObj) {
		// get canvas.
		var canvas = this.getCanvas();
		// Try to get the focus
		canvas.focus()

		// find the shape that is responsible for this element's id.
		var element = event.currentTarget;
		var elementController = uiObj;

		// gather information on selection.
		var currentIsSelectable = (elementController !== null) &&
			(elementController !== undefined) && (elementController.isSelectable);
		var currentIsMovable = (elementController !== null) &&
			(elementController !== undefined) && (elementController.isMovable);
		var modifierKeyPressed = event.shiftKey || event.ctrlKey;
		var noObjectsSelected = this.selection.length === 0;
		var currentIsSelected = this.selection.member(elementController);


		// Rule #1: When there is nothing selected, select the clicked object.
		if (currentIsSelectable && noObjectsSelected) {

			this.setSelection([elementController]);

			ORYX.Log.trace("Rule #1 applied for mouse down on %0", element.id);

			// Rule #3: When at least one element is selected, and there is no
			// control key pressed, and the clicked object is not selected, select
			// the clicked object.
		} else if (currentIsSelectable && !noObjectsSelected &&
			!modifierKeyPressed && !currentIsSelected) {

			this.setSelection([elementController]);

			//var objectType = elementController.readAttributes();
			//alert(objectType[0] + ": " + objectType[1]);

			ORYX.Log.trace("Rule #3 applied for mouse down on %0", element.id);

			// Rule #4: When the control key is pressed, and the current object is
			// not selected, add it to the selection.
		} else if (currentIsSelectable && modifierKeyPressed
			&& !currentIsSelected) {

			var newSelection = this.selection.clone();
			newSelection.push(elementController)
			this.setSelection(newSelection)

			ORYX.Log.trace("Rule #4 applied for mouse down on %0", element.id);

			// Rule #6
		} else if (currentIsSelectable && currentIsSelected &&
			modifierKeyPressed) {

			var newSelection = this.selection.clone();
			this.setSelection(newSelection.without(elementController))

			ORYX.Log.trace("Rule #6 applied for mouse down on %0", elementController.id);

			// Rule #5: When there is at least one object selected and no control
			// key pressed, we're dragging.
			/*} else if(currentIsSelectable && !noObjectsSelected
				&& !modifierKeyPressed) {

				if(this.log.isTraceEnabled())
					this.log.trace("Rule #5 applied for mouse down on "+element.id);
	*/
			// Rule #2: When clicked on something that is neither
			// selectable nor movable, clear the selection, and return.
		} else if (!currentIsSelectable && !currentIsMovable) {

			this.setSelection([]);

			ORYX.Log.trace("Rule #2 applied for mouse down on %0", element.id);

			return;

			// Rule #7: When the current object is not selectable but movable,
			// it is probably a control. Leave the selection unchanged but set
			// the movedObject to the current one and enable Drag. Dockers will
			// be processed in the dragDocker plugin.
		} else if (!currentIsSelectable && currentIsMovable && !(elementController instanceof ORYX.Core.Controls.Docker)) {

			// TODO: If there is any moveable elements, do this in a plugin
			//ORYX.Core.UIEnableDrag(event, elementController);

			ORYX.Log.trace("Rule #7 applied for mouse down on %0", element.id);

			// Rule #8: When the element is selectable and is currently selected and no 
			// modifier key is pressed
		} else if (currentIsSelectable && currentIsSelected &&
			!modifierKeyPressed) {

			this._subSelection = this._subSelection != elementController ? elementController : undefined;

			this.setSelection(this.selection, this._subSelection);

			ORYX.Log.trace("Rule #8 applied for mouse down on %0", element.id);
		}


		// prevent event from bubbling, return.
		//Event.stop(event);
		return;
	},

	_handleMouseMove: function (event, uiObj) {
		return;
	},

	_handleMouseUp: function (event, uiObj) {
		// get canvas.
		var canvas = this.getCanvas();

		// find the shape that is responsible for this elemement's id.
		var elementController = uiObj;

		//get event position
		var evPos = this.eventCoordinates(event);

		//Event.stop(event);
	},

	_handleMouseHover: function (event, uiObj) {
		return;
	},

	_handleMouseOut: function (event, uiObj) {
		return;
	},

	/**
	 * Calculates the event coordinates to SVG document coordinates.
	 * @param {Event} event
	 * @return {SVGPoint} The event coordinates in the SVG document
	 */
	eventCoordinates: function (event) {

		var canvas = this.getCanvas();

		var svgPoint = canvas.node.ownerSVGElement.createSVGPoint();
		svgPoint.x = event.clientX;
		svgPoint.y = event.clientY;
		var matrix = canvas.node.getScreenCTM();
		return svgPoint.matrixTransform(matrix.inverse());
	}
};
ORYX.Editor = Clazz.extend(ORYX.Editor);

var ORYX_Editor = null;
/**
 * 获取当前的Editor实例
 */
function getEditor() {
	return ORYX_Editor;
}

/**
 * Creates a new ORYX.Editor instance by fetching a model from given url and passing it to the constructur
 * @param {String} modelUrl The JSON URL of a model.
 * @param {Object} config Editor config passed to the constructur, merged with the response of the request to modelUrl
 */
ORYX.Editor.createByUrl = function (modelUrl, config) {
	if (!config) config = {};

	new Ajax.Request(modelUrl, {
		method: 'GET',
		onSuccess: function (transport) {
			var editorConfig = Ext.decode(transport.responseText);
			editorConfig = Ext.applyIf(editorConfig, config);
			ORYX_Editor = new ORYX.Editor(editorConfig);
		}.bind(this)
	});
}

// TODO Implement namespace awareness on attribute level.
/**
 * graft() function
 * Originally by Sean M. Burke from interglacial.com, altered for usage with
 * SVG and namespace (xmlns) support. Be sure you understand xmlns before
 * using this funtion, as it creates all grafted elements in the xmlns
 * provided by you and all element's attribures in default xmlns. If you
 * need to graft elements in a certain xmlns and wish to assign attributes
 * in both that and another xmlns, you will need to do stepwise grafting,
 * adding non-default attributes yourself or you'll have to enhance this
 * function. Latter, I would appreciate: martin�apfelfabrik.de
 * @param {Object} namespace The namespace in which
 * 					elements should be grafted.
 * @param {Object} parent The element that should contain the grafted
 * 					structure after the function returned.
 * @param {Object} t the crafting structure.
 * @param {Object} doc the document in which grafting is performed.
 */
ORYX.Editor.graft = function (namespace, parent, t, doc) {

	doc = (doc || (parent && parent.ownerDocument) || document);
	var e;
	if (t === undefined) {
		throw "Can't graft an undefined value";
	} else if (t.constructor == String) {
		e = doc.createTextNode(t);
	} else {
		for (var i = 0; i < t.length; i++) {
			if (i === 0 && t[i].constructor == String) {
				var snared;
				snared = t[i].match(/^([a-z][a-z0-9]*)\.([^\s\.]+)$/i);
				if (snared) {
					e = doc.createElementNS(namespace, snared[1]);
					e.setAttributeNS(null, 'class', snared[2]);
					continue;
				}
				snared = t[i].match(/^([a-z][a-z0-9]*)$/i);
				if (snared) {
					e = doc.createElementNS(namespace, snared[1]);  // but no class
					continue;
				}

				// Otherwise:
				e = doc.createElementNS(namespace, "span");
				e.setAttribute(null, "class", "namelessFromLOL");
			}

			if (t[i] === undefined) {
				throw "Can't graft an undefined value in a list!";
			} else if (t[i].constructor == String || t[i].constructor == Array) {
				this.graft(namespace, e, t[i], doc);
			} else if (t[i].constructor == Number) {
				this.graft(namespace, e, t[i].toString(), doc);
			} else if (t[i].constructor == Object) {
				// hash's properties => element's attributes
				for (var k in t[i]) { e.setAttributeNS(null, k, t[i][k]); }
			} else {

			}
		}
	}
	if (parent) {
		parent.appendChild(e);
	} else {

	}
	return e; // return the topmost created node
};

ORYX.Editor.provideId = function () {
	var res = [], hex = '0123456789ABCDEF';

	for (var i = 0; i < 36; i++) res[i] = Math.floor(Math.random() * 0x10);

	res[14] = 4;
	res[19] = (res[19] & 0x3) | 0x8;

	for (var i = 0; i < 36; i++) res[i] = hex[res[i]];

	res[8] = res[13] = res[18] = res[23] = '-';

	return "oryx_" + res.join('');
};

/**
 * When working with Ext, conditionally the window needs to be resized. To do
 * so, use this class method. Resize is deferred until 100ms, and all subsequent
 * resizeBugFix calls are ignored until the initially requested resize is
 * performed.
 */
ORYX.Editor.resizeFix = function () {
	if (!ORYX.Editor._resizeFixTimeout) {
		ORYX.Editor._resizeFixTimeout = window.setTimeout(function () {
			window.resizeBy(1, 1);
			window.resizeBy(-1, -1);
			ORYX.Editor._resizefixTimeout = null;
		}, 100);
	}
};

ORYX.Editor.Cookie = {

	callbacks: [],

	onChange: function (callback, interval) {

		this.callbacks.push(callback);
		this.start(interval)

	},

	start: function (interval) {

		if (this.pe) {
			return;
		}

		var currentString = document.cookie;

		this.pe = new PeriodicalExecuter(function () {

			if (currentString != document.cookie) {
				currentString = document.cookie;
				this.callbacks.each(function (callback) { callback(this.getParams()) }.bind(this));
			}

		}.bind(this), (interval || 10000) / 1000);
	},

	stop: function () {

		if (this.pe) {
			this.pe.stop();
			this.pe = null;
		}
	},

	getParams: function () {
		var res = {};

		var p = document.cookie;
		p.split("; ").each(function (param) { res[param.split("=")[0]] = param.split("=")[1]; });

		return res;
	},

	toString: function () {
		return document.cookie;
	}
};

/**
 * Workaround for SAFARI/Webkit, because
 * when trying to check SVGSVGElement of instanceof there is 
 * raising an error
 * 
 */
ORYX.Editor.SVGClassElementsAreAvailable = true;
ORYX.Editor.setMissingClasses = function () {

	try {
		SVGElement;
	} catch (e) {
		ORYX.Editor.SVGClassElementsAreAvailable = false;
		SVGSVGElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg').toString();
		SVGGElement = document.createElementNS('http://www.w3.org/2000/svg', 'g').toString();
		SVGPathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path').toString();
		SVGTextElement = document.createElementNS('http://www.w3.org/2000/svg', 'text').toString();
		//SVGMarkerElement 	= document.createElementNS('http://www.w3.org/2000/svg', 'marker').toString();
		SVGRectElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect').toString();
		SVGImageElement = document.createElementNS('http://www.w3.org/2000/svg', 'image').toString();
		SVGCircleElement = document.createElementNS('http://www.w3.org/2000/svg', 'circle').toString();
		SVGEllipseElement = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse').toString();
		SVGLineElement = document.createElementNS('http://www.w3.org/2000/svg', 'line').toString();
		SVGPolylineElement = document.createElementNS('http://www.w3.org/2000/svg', 'polyline').toString();
		SVGPolygonElement = document.createElementNS('http://www.w3.org/2000/svg', 'polygon').toString();

	}

}
ORYX.Editor.checkClassType = function (classInst, classType) {

	if (ORYX.Editor.SVGClassElementsAreAvailable) {
		return classInst instanceof classType
	} else {
		return classInst == classType
	}
};
ORYX.Editor.makeExtModalWindowKeysave = function (facade) {
	Ext.override(Ext.Window, {
		beforeShow: function () {
			delete this.el.lastXY;
			delete this.el.lastLT;
			if (this.x === undefined || this.y === undefined) {
				var xy = this.el.getAlignToXY(this.container, 'c-c');
				var pos = this.el.translatePoints(xy[0], xy[1]);
				this.x = this.x === undefined ? pos.left : this.x;
				this.y = this.y === undefined ? pos.top : this.y;
			}
			this.el.setLeftTop(this.x, this.y);

			if (this.expandOnShow) {
				this.expand(false);
			}

			if (this.modal) {
				facade.disableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
				Ext.getBody().addClass("x-body-masked");
				this.mask.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
				this.mask.show();
			}
		},
		afterHide: function () {
			this.proxy.hide();
			if (this.monitorResize || this.modal || this.constrain || this.constrainHeader) {
				Ext.EventManager.removeResizeListener(this.onWindowResize, this);
			}
			if (this.modal) {
				this.mask.hide();
				facade.enableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
				Ext.getBody().removeClass("x-body-masked");
			}
			if (this.keyMap) {
				this.keyMap.disable();
			}
			this.fireEvent("hide", this);
		},
		beforeDestroy: function () {
			if (this.modal)
				facade.enableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
			Ext.destroy(
				this.resizer,
				this.dd,
				this.proxy,
				this.mask
			);
			Ext.Window.superclass.beforeDestroy.call(this);
		}
	});
}
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }


new function () {

	ORYX.Core.UIEnableDrag = function (event, uiObj, option) {

		this.uiObj = uiObj;
		var upL = uiObj.bounds.upperLeft();

		var a = uiObj.node.getScreenCTM();
		this.faktorXY = { x: a.a, y: a.d };

		this.scrollNode = uiObj.node.ownerSVGElement.parentNode.parentNode;

		this.offSetPosition = {
			x: Event.pointerX(event) - (upL.x * this.faktorXY.x),
			y: Event.pointerY(event) - (upL.y * this.faktorXY.y)
		};

		this.offsetScroll = { x: this.scrollNode.scrollLeft, y: this.scrollNode.scrollTop };

		this.dragCallback = ORYX.Core.UIDragCallback.bind(this);
		this.disableCallback = ORYX.Core.UIDisableDrag.bind(this);

		this.movedCallback = option ? option.movedCallback : undefined;
		this.upCallback = option ? option.upCallback : undefined;

		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this.disableCallback, true);
		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this.dragCallback, false);

	};

	ORYX.Core.UIDragCallback = function (event) {

		var position = {
			x: Event.pointerX(event) - this.offSetPosition.x,
			y: Event.pointerY(event) - this.offSetPosition.y
		}

		position.x -= this.offsetScroll.x - this.scrollNode.scrollLeft;
		position.y -= this.offsetScroll.y - this.scrollNode.scrollTop;

		position.x /= this.faktorXY.x;
		position.y /= this.faktorXY.y;

		this.uiObj.bounds.moveTo(position);
		//this.uiObj.update();

		if (this.movedCallback)
			this.movedCallback(event);

		Event.stop(event);

	};

	ORYX.Core.UIDisableDrag = function (event) {
		document.documentElement.removeEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this.dragCallback, false);
		document.documentElement.removeEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this.disableCallback, true);

		if (this.upCallback)
			this.upCallback(event);

		this.upCallback = undefined;
		this.movedCallback = undefined;

		Event.stop(event);
	};
	/**
	 * Implements a command to move docker by an offset.
	 * 
	 * @class ORYX.Core.MoveDockersCommand
	 * @param {Object} object An object with the docker id as key and docker and offset as object value
	 * 
	 */
	ORYX.Core.MoveDockersCommand = ORYX.Core.Command.extend({
		construct: function (dockers) {
			this.dockers = $H(dockers);
			this.edges = $H({});
		},
		execute: function () {
			if (this.changes) {
				this.executeAgain();
				return;
			} else {
				this.changes = $H({});
			}

			this.dockers.values().each(function (docker) {
				var edge = docker.docker.parent;
				if (!edge) { return }

				if (!this.changes[edge.getId()]) {
					this.changes[edge.getId()] = {
						edge: edge,
						oldDockerPositions: edge.dockers.map(function (r) { return r.bounds.center() })
					}
				}
				docker.docker.bounds.moveBy(docker.offset);
				this.edges[edge.getId()] = edge;
				docker.docker.update();
			}.bind(this));
			this.edges.each(function (edge) {
				this.updateEdge(edge.value);
				if (this.changes[edge.value.getId()])
					this.changes[edge.value.getId()].dockerPositions = edge.value.dockers.map(function (r) { return r.bounds.center() })
			}.bind(this));
		},
		updateEdge: function (edge) {
			edge._update(true);
			[edge.getOutgoingShapes(), edge.getIncomingShapes()].flatten().invoke("_update", [true])
		},
		executeAgain: function () {
			this.changes.values().each(function (change) {
				// Reset the dockers
				this.removeAllDocker(change.edge);
				change.dockerPositions.each(function (pos, i) {
					if (i == 0 || i == change.dockerPositions.length - 1) { return }
					var docker = change.edge.createDocker(undefined, pos);
					docker.bounds.centerMoveTo(pos);
					docker.update();
				}.bind(this));
				this.updateEdge(change.edge);
			}.bind(this));
		},
		rollback: function () {
			this.changes.values().each(function (change) {
				// Reset the dockers
				this.removeAllDocker(change.edge);
				change.oldDockerPositions.each(function (pos, i) {
					if (i == 0 || i == change.oldDockerPositions.length - 1) { return }
					var docker = change.edge.createDocker(undefined, pos);
					docker.bounds.centerMoveTo(pos);
					docker.update();
				}.bind(this));
				this.updateEdge(change.edge);
			}.bind(this));
		},
		removeAllDocker: function (edge) {
			edge.dockers.slice(1, edge.dockers.length - 1).each(function (docker) {
				edge.removeDocker(docker);
			})
		}
	});

}();
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }

/**
 * @classDescription Base class for Shapes.
 * 基本的图形元素基类
 * @extends ORYX.Core.AbstractShape
 */
ORYX.Core.Shape = {

	/**
	 * Constructor
	 */
	construct: function (options, stencil) {
		// call base class constructor
		arguments.callee.$.construct.apply(this, arguments);

		this.dockers = [];
		this.magnets = [];

		this._defaultMagnet;

		this.incoming = [];
		this.outgoing = [];

		this.nodes = [];

		this._dockerChangedCallback = this._dockerChanged.bind(this);

		//Hash map for all labels. Labels are not treated as children of shapes.
		this._labels = new Hash();

		// create SVG node
		this.node = ORYX.Editor.graft("http://www.w3.org/2000/svg",
			null,
			['g', { id: "svg-" + this.resourceId },
				['g', { "class": "stencils" },
					['g', { "class": "me" }],
					['g', { "class": "children", style: "overflow:hidden" }],
					['g', { "class": "edge" }]
				],
				['g', { "class": "controls" },
					['g', { "class": "dockers" }],
					['g', { "class": "magnets" }]
				]
			]);
	},

	/**
	 * If changed flag is set, refresh method is called.
	 */
	update: function () {
		//if(this.isChanged) {
		//this.layout();
		//}
	},

	/**
	 * !!!Not called from any sub class!!!
	 */
	_update: function () {

	},

	/**
	 * Calls the super class refresh method
	 *  and updates the svg elements that are referenced by a property.
	 */
	refresh: function () {
		//call base class refresh method
		arguments.callee.$.refresh.apply(this, arguments);

		if (this.node.ownerDocument) {
			//adjust SVG to properties' values
			var me = this;
			this.propertiesChanged.each((function (propChanged) {
				if (propChanged.value) {
					var prop = this.properties[propChanged.key];
					var property = this.getStencil().property(propChanged.key);
					if (property != undefined) {
						this.propertiesChanged[propChanged.key] = false;

						//handle choice properties
						if (property.type() == ORYX.CONFIG.TYPE_CHOICE) {
							//iterate all references to SVG elements
							property.refToView().each((function (ref) {
								//if property is referencing a label, update the label
								if (ref !== "") {
									var label = this._labels[this.id + ref];
									if (label && property.item(prop)) {
										label.text(property.item(prop).title());
									}
								}
							}).bind(this));

							//if the choice's items are referencing SVG elements
							// show the selected and hide all other referenced SVG
							// elements
							var refreshedSvgElements = new Hash();
							property.items().each((function (item) {
								item.refToView().each((function (itemRef) {
									if (itemRef == "") { return; }

									var svgElem = this.node.ownerDocument.getElementById(this.id + itemRef);

									if (!svgElem) { return; }


									/* Do not refresh the same svg element multiple times */
									if (!refreshedSvgElements[svgElem.id] || prop == item.value()) {
										svgElem.setAttributeNS(null, 'display', ((prop == item.value()) ? 'inherit' : 'none'));
										refreshedSvgElements[svgElem.id] = svgElem;
									}

									// Reload the href if there is an image-tag
									if (ORYX.Editor.checkClassType(svgElem, SVGImageElement)) {
										svgElem.setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgElem.getAttributeNS('http://www.w3.org/1999/xlink', 'href'));
									}
								}).bind(this));
							}).bind(this));

						} else { //handle properties that are not of type choice
							//iterate all references to SVG elements
							property.refToView().each((function (ref) {
								//if the property does not reference an SVG element,
								// do nothing

								if (ref === "") { return; }

								var refId = this.id + ref;

								//get the SVG element
								var svgElem = this.node.ownerDocument.getElementById(refId);

								//if the SVG element can not be found
								if (!svgElem || !(svgElem.ownerSVGElement)) {
									//if the referenced SVG element is a SVGAElement, it cannot
									// be found with getElementById (Firefox bug).
									// this is a work around
									if (property.type() === ORYX.CONFIG.TYPE_URL || property.type() === ORYX.CONFIG.TYPE_DIAGRAM_LINK) {
										var svgElems = this.node.ownerDocument.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'a');

										svgElem = $A(svgElems).find(function (elem) {
											return elem.getAttributeNS(null, 'id') === refId;
										});

										if (!svgElem) { return; }
									} else {
										//this.propertiesChanged[propChanged.key] = true;
										return;
									}
								}

								if (property.complexAttributeToView()) {
									var label = this._labels[refId];
									if (label) {
										try {
											propJson = prop.evalJSON();
											var value = propJson[property.complexAttributeToView()]
											label.text(value ? value : prop);
										} catch (e) {
											label.text(prop);
										}
									}

								} else {

									switch (property.type()) {
										case ORYX.CONFIG.TYPE_BOOLEAN:

											if (typeof prop == "string")
												prop = prop === "true"

											svgElem.setAttributeNS(null, 'display', (!(prop === property.inverseBoolean())) ? 'inherit' : 'none');

											break;
										case ORYX.CONFIG.TYPE_COLOR:
											if (property.fill()) {
												if (svgElem.tagName.toLowerCase() === "stop") {
													if (prop) {

														if (property.lightness() && property.lightness() !== 1) {
															prop = ORYX.Utils.adjustLightness(prop, property.lightness());
														}

														svgElem.setAttributeNS(null, "stop-color", prop);

														// Adjust stop color of the others
														if (svgElem.parentNode.tagName.toLowerCase() === "radialgradient") {
															ORYX.Utils.adjustGradient(svgElem.parentNode, svgElem);
														}
													}

													// If there is no value, set opaque
													if (svgElem.parentNode.tagName.toLowerCase() === "radialgradient") {
														$A(svgElem.parentNode.getElementsByTagName('stop')).each(function (stop) {
															stop.setAttributeNS(null, "stop-opacity", prop ? stop.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, 'default-stop-opacity') || 1 : 0);
														}.bind(this))
													}
												} else {
													svgElem.setAttributeNS(null, 'fill', prop);
												}
											}
											if (property.stroke()) {
												svgElem.setAttributeNS(null, 'stroke', prop);
											}
											break;
										case ORYX.CONFIG.TYPE_STRING:
											var label = this._labels[refId];
											if (label) {
												label.text(prop);
											}
											break;
										case ORYX.CONFIG.TYPE_INTEGER:
											var label = this._labels[refId];
											if (label) {
												label.text(prop);
											}
											break;
										case ORYX.CONFIG.TYPE_FLOAT:
											if (property.fillOpacity()) {
												svgElem.setAttributeNS(null, 'fill-opacity', prop);
											}
											if (property.strokeOpacity()) {
												svgElem.setAttributeNS(null, 'stroke-opacity', prop);
											}
											if (!property.fillOpacity() && !property.strokeOpacity()) {
												var label = this._labels[refId];
												if (label) {
													label.text(prop);
												}
											}
											break;
										case ORYX.CONFIG.TYPE_URL:
										case ORYX.CONFIG.TYPE_DIAGRAM_LINK:
											//TODO what is the dafault path?
											var hrefAttr = svgElem.getAttributeNodeNS('http://www.w3.org/1999/xlink', 'xlink:href');
											if (hrefAttr) {
												hrefAttr.textContent = prop;
											} else {
												svgElem.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', prop);
											}
											break;
									}
								}
							}).bind(this));


						}
					}

				}
			}).bind(this));

			//update labels
			this._labels.values().each(function (label) {
				label.update();
			});
		}
	},

	layout: function () {
		//this.getStencil().layout(this)
		var layoutEvents = this.getStencil().layout()
		if (layoutEvents) {
			layoutEvents.each(function (event) {

				// setup additional attributes
				event.shape = this;
				event.forceExecution = true;

				// do layouting
				this._delegateEvent(event);
			}.bind(this))

		}
	},

	/**
	 * Returns an array of Label objects.
	 */
	getLabels: function () {
		return this._labels.values();
	},

	/**
	 * Returns the label for a given ref
	 * @return {ORYX.Core.Label} Returns null if there is no label
	 */
	getLabel: function (ref) {
		if (!ref) {
			return null;
		}
		return (this._labels.find(function (o) {
			return o.key.endsWith(ref);
		}) || {}).value || null;
	},

	/**
	 * Hides all related labels
	 * 
	 */
	hideLabels: function () {
		this.getLabels().invoke("hide");
	},

	/**
	 * Shows all related labels
	 * 
	 */
	showLabels: function () {
		var labels = this.getLabels();
		labels.invoke("show");
		labels.each(function (label) {
			label.update();
		});
	},

	setOpacity: function (value, animate) {

		// 0.0 <= value <= 1.0
		value = Math.max(Math.min((typeof value == "number" ? value : 1.0), 1.0), 0.0);

		//if (animate !== true){
		if (value !== 1.0) {
			value = String(value);
			this.node.setAttributeNS(null, "fill-opacity", value)
			this.node.setAttributeNS(null, "stroke-opacity", value)
		} else {
			this.node.removeAttributeNS(null, "fill-opacity");
			this.node.removeAttributeNS(null, "stroke-opacity");
		}
		/*} else {
			var args = {opacity:{to:value}};
			if (!this.isVisible){
				this.show();
			}
			if (this.currentAnim){
				this.currentAnim.stop();
			}
			
			this.currentAnim = Ext.lib.Anim.run(this.node, args, 0.4, "easeOut", function(){
			    if (args.opacity.to === 0.0){
			        this.hide();
			    } else if (args.opacity.to === 1.0){
					this.node.removeAttributeNS(null, "style");
				}
				delete this.currentAnim;
			}, this)
		}*/



	},

	/**
	 * Returns an array of dockers of this object.
	 */
	getDockers: function () {
		return this.dockers;
	},

	getMagnets: function () {
		return this.magnets;
	},

	getDefaultMagnet: function () {
		if (this._defaultMagnet) {
			return this._defaultMagnet;
		} else if (this.magnets.length > 0) {
			return this.magnets[0];
		} else {
			return undefined;
		}
	},

	getParentShape: function () {
		return this.parent;
	},

	getIncomingShapes: function (iterator) {
		if (iterator) {
			this.incoming.each(iterator);
		}
		return this.incoming;
	},

	getIncomingNodes: function (iterator) {
		return this.incoming.select(function (incoming) {
			var isNode = (incoming instanceof ORYX.Core.Node);
			if (isNode && iterator) iterator(incoming);
			return isNode;
		});
	},


	getOutgoingShapes: function (iterator) {
		if (iterator) {
			this.outgoing.each(iterator);
		}
		return this.outgoing;
	},

	getOutgoingNodes: function (iterator) {
		return this.outgoing.select(function (out) {
			var isNode = (out instanceof ORYX.Core.Node);
			if (isNode && iterator) iterator(out);
			return isNode;
		});
	},

	getAllDockedShapes: function (iterator) {
		var result = this.incoming.concat(this.outgoing);
		if (iterator) {
			result.each(iterator);
		}
		return result
	},

	getCanvas: function () {
		if (this.parent instanceof ORYX.Core.Canvas) {
			return this.parent;
		} else if (this.parent instanceof ORYX.Core.Shape) {
			return this.parent.getCanvas();
		} else {
			return undefined;
		}
	},

	/**
	 * 
	 * @param {Object} deep
	 * @param {Object} iterator
	 */
	getChildNodes: function (deep, iterator) {
		if (!deep && !iterator) {
			return this.nodes.clone();
		} else {
			var result = [];
			this.nodes.each(function (uiObject) {
				if (!uiObject.isVisible) { return }
				if (iterator) {
					iterator(uiObject);
				}
				result.push(uiObject);

				if (deep && uiObject instanceof ORYX.Core.Shape) {
					result = result.concat(uiObject.getChildNodes(deep, iterator));
				}
			});

			return result;
		}
	},

	/**
	 * Overrides the UIObject.add method. Adds uiObject to the correct sub node.
	 * @param {UIObject} uiObject
	 * @param {Number} index
	 */
	add: function (uiObject, index, silent) {
		//parameter has to be an UIObject, but
		// must not be an Edge.
		if (uiObject instanceof ORYX.Core.UIObject
			&& !(uiObject instanceof ORYX.Core.Edge)) {

			if (!(this.children.member(uiObject))) {
				//if uiObject is child of another parent, remove it from that parent.
				if (uiObject.parent) {
					uiObject.parent.remove(uiObject, true);
				}

				//add uiObject to this Shape
				if (index != undefined)
					this.children.splice(index, 0, uiObject);
				else
					this.children.push(uiObject);

				//set parent reference
				uiObject.parent = this;

				//add uiObject.node to this.node depending on the type of uiObject
				var parent;
				if (uiObject instanceof ORYX.Core.Node) {
					parent = this.node.childNodes[0].childNodes[1];
					this.nodes.push(uiObject);
				} else if (uiObject instanceof ORYX.Core.Controls.Control) {
					var ctrls = this.node.childNodes[1];
					if (uiObject instanceof ORYX.Core.Controls.Docker) {
						parent = ctrls.childNodes[0];
						if (this.dockers.length >= 2) {
							this.dockers.splice(index !== undefined ? Math.min(index, this.dockers.length - 1) : this.dockers.length - 1, 0, uiObject);
						} else {
							this.dockers.push(uiObject);
						}
					} else if (uiObject instanceof ORYX.Core.Controls.Magnet) {
						parent = ctrls.childNodes[1];
						this.magnets.push(uiObject);
					} else {
						parent = ctrls;
					}
				} else {	//UIObject
					parent = this.node;
				}

				if (index != undefined && index < parent.childNodes.length)
					uiObject.node = parent.insertBefore(uiObject.node, parent.childNodes[index]);
				else
					uiObject.node = parent.appendChild(uiObject.node);

				this._changed();
				//uiObject.bounds.registerCallback(this._changedCallback);


				if (this.eventHandlerCallback && silent !== true)
					this.eventHandlerCallback({ type: ORYX.CONFIG.EVENT_SHAPEADDED, shape: uiObject })

			} else {

				ORYX.Log.warn("add: ORYX.Core.UIObject is already a child of this object.");
			}
		} else {

			ORYX.Log.warn("add: Parameter is not of type ORYX.Core.UIObject.");
		}
	},

	/**
	 * Overrides the UIObject.remove method. Removes uiObject.
	 * @param {UIObject} uiObject
	 */
	remove: function (uiObject, silent) {
		//if uiObject is a child of this object, remove it.
		if (this.children.member(uiObject)) {
			//remove uiObject from children
			var parent = uiObject.parent;

			this.children = this.children.without(uiObject);

			//delete parent reference of uiObject
			uiObject.parent = undefined;

			//delete uiObject.node from this.node
			if (uiObject instanceof ORYX.Core.Shape) {
				if (uiObject instanceof ORYX.Core.Edge) {
					uiObject.removeMarkers();
					uiObject.node = this.node.childNodes[0].childNodes[2].removeChild(uiObject.node);
				} else {
					uiObject.node = this.node.childNodes[0].childNodes[1].removeChild(uiObject.node);
					this.nodes = this.nodes.without(uiObject);
				}
			} else if (uiObject instanceof ORYX.Core.Controls.Control) {
				if (uiObject instanceof ORYX.Core.Controls.Docker) {
					uiObject.node = this.node.childNodes[1].childNodes[0].removeChild(uiObject.node);
					this.dockers = this.dockers.without(uiObject);
				} else if (uiObject instanceof ORYX.Core.Controls.Magnet) {
					uiObject.node = this.node.childNodes[1].childNodes[1].removeChild(uiObject.node);
					this.magnets = this.magnets.without(uiObject);
				} else {
					uiObject.node = this.node.childNodes[1].removeChild(uiObject.node);
				}
			}

			if (this.eventHandlerCallback && silent !== true)
				this.eventHandlerCallback({ type: ORYX.CONFIG.EVENT_SHAPEREMOVED, shape: uiObject, parent: parent });

			this._changed();
			//uiObject.bounds.unregisterCallback(this._changedCallback);
		} else {

			ORYX.Log.warn("remove: ORYX.Core.UIObject is not a child of this object.");
		}
	},

	/**
	 * Calculate the Border Intersection Point between two points
	 * @param {PointA}
	 * @param {PointB}
	 */
	getIntersectionPoint: function () {

		var pointAX, pointAY, pointBX, pointBY;

		// Get the the two Points	
		switch (arguments.length) {
			case 2:
				pointAX = arguments[0].x;
				pointAY = arguments[0].y;
				pointBX = arguments[1].x;
				pointBY = arguments[1].y;
				break;
			case 4:
				pointAX = arguments[0];
				pointAY = arguments[1];
				pointBX = arguments[2];
				pointBY = arguments[3];
				break;
			default:
				throw "getIntersectionPoints needs two or four arguments";
		}



		// Defined an include and exclude point
		var includePointX, includePointY, excludePointX, excludePointY;

		var bounds = this.absoluteBounds();

		if (this.isPointIncluded(pointAX, pointAY, bounds)) {
			includePointX = pointAX;
			includePointY = pointAY;
		} else {
			excludePointX = pointAX;
			excludePointY = pointAY;
		}

		if (this.isPointIncluded(pointBX, pointBY, bounds)) {
			includePointX = pointBX;
			includePointY = pointBY;
		} else {
			excludePointX = pointBX;
			excludePointY = pointBY;
		}

		// If there is no inclue or exclude Shape, than return
		if (!includePointX || !includePointY || !excludePointX || !excludePointY) {
			return undefined;
		}

		var midPointX = 0;
		var midPointY = 0;

		var refPointX, refPointY;

		var minDifferent = 1;
		// Get the UpperLeft and LowerRight
		//var ul = bounds.upperLeft();
		//var lr = bounds.lowerRight();

		var i = 0;

		while (true) {
			// Calculate the midpoint of the current to points	
			var midPointX = Math.min(includePointX, excludePointX) + ((Math.max(includePointX, excludePointX) - Math.min(includePointX, excludePointX)) / 2.0);
			var midPointY = Math.min(includePointY, excludePointY) + ((Math.max(includePointY, excludePointY) - Math.min(includePointY, excludePointY)) / 2.0);


			// Set the new midpoint by the means of the include of the bounds
			if (this.isPointIncluded(midPointX, midPointY, bounds)) {
				includePointX = midPointX;
				includePointY = midPointY;
			} else {
				excludePointX = midPointX;
				excludePointY = midPointY;
			}

			// Calc the length of the line
			var length = Math.sqrt(Math.pow(includePointX - excludePointX, 2) + Math.pow(includePointY - excludePointY, 2))
			// Calc a point one step from the include point
			refPointX = includePointX + ((excludePointX - includePointX) / length),
				refPointY = includePointY + ((excludePointY - includePointY) / length)


			// If the reference point not in the bounds, break
			if (!this.isPointIncluded(refPointX, refPointY, bounds)) {
				break
			}


		}

		// Return the last includepoint
		return { x: refPointX, y: refPointY };
	},



    /**
     * Calculate if the point is inside the Shape
     * @param {PointX}
     * @param {PointY} 
     */
	isPointIncluded: function () {
		return false
	},

	/**
	 * Returns TRUE if the given node
	 * is a child node of the shapes node
	 * @param {Element} node
	 * @return {Boolean}
	 *
	 */
	containsNode: function (node) {
		var me = this.node.firstChild.firstChild;
		while (node) {
			if (node == me) {
				return true;
			}
			node = node.parentNode;
		}
		return false
	},

    /**
     * Calculate if the point is over an special offset area
     * @param {Point}
     */
	isPointOverOffset: function () {
		return this.isPointIncluded.apply(this, arguments)
	},

	_dockerChanged: function () {

	},

	/**
	 * Create a Docker for this Edge
	 *
	 */
	createDocker: function (index, position) {
		var docker = new ORYX.Core.Controls.Docker({ eventHandlerCallback: this.eventHandlerCallback });
		docker.bounds.registerCallback(this._dockerChangedCallback);
		if (position) {
			docker.bounds.centerMoveTo(position);
		}
		this.add(docker, index);

		return docker
	},

	/**
	 * Get the serialized object
	 * return Array with hash-entrees (prefix, name, value)
	 * Following values will given:
	 * 		Bounds
	 * 		Outgoing Shapes
	 * 		Parent
	 */
	serialize: function () {
		var serializedObject = arguments.callee.$.serialize.apply(this);

		// Add the bounds
		serializedObject.push({ name: 'bounds', prefix: 'oryx', value: this.bounds.serializeForERDF(), type: 'literal' });

		// Add the outgoing shapes
		this.getOutgoingShapes().each((function (followingShape) {
			serializedObject.push({ name: 'outgoing', prefix: 'raziel', value: '#' + ERDF.__stripHashes(followingShape.resourceId), type: 'resource' });
		}).bind(this));

		// Add the parent shape, if the parent not the canvas
		//if(this.parent instanceof ORYX.Core.Shape){
		serializedObject.push({ name: 'parent', prefix: 'raziel', value: '#' + ERDF.__stripHashes(this.parent.resourceId), type: 'resource' });
		//}			

		return serializedObject;
	},


	deserialize: function (serialize, json) {
		arguments.callee.$.deserialize.apply(this, arguments);

		// Set the Bounds
		var bounds = serialize.find(function (ser) { return 'oryx-bounds' === (ser.prefix + "-" + ser.name) });
		if (bounds) {
			var b = bounds.value.replace(/,/g, " ").split(" ").without("");
			if (this instanceof ORYX.Core.Edge) {
				if (!this.dockers.first().isChanged)
					this.dockers.first().bounds.centerMoveTo(parseFloat(b[0]), parseFloat(b[1]));
				if (!this.dockers.last().isChanged)
					this.dockers.last().bounds.centerMoveTo(parseFloat(b[2]), parseFloat(b[3]));
			} else {
				this.bounds.set(parseFloat(b[0]), parseFloat(b[1]), parseFloat(b[2]), parseFloat(b[3]));
			}
		}

		if (json && json.labels instanceof Array) {
			json.labels.each(function (slabel) {
				var label = this.getLabel(slabel.ref);
				if (label) {
					label.deserialize(slabel, this);
				}
			}.bind(this))
		}
	},

	toJSON: function () {
		var json = arguments.callee.$.toJSON.apply(this, arguments);

		var labels = [], id = this.id;
		this._labels.each(function (obj) {
			var slabel = obj.value.serialize();
			if (slabel) {
				slabel.ref = obj.key.replace(id, '');
				labels.push(slabel);
			}
		});

		if (labels.length > 0) {
			json.labels = labels;
		}
		return json;
	},


	/**
	 * Private methods.
	 */

	/**
	 * Child classes have to overwrite this method for initializing a loaded
	 * SVG representation.
	 * @param {SVGDocument} svgDocument
	 */
	_init: function (svgDocument) {
		//adjust ids
		this._adjustIds(svgDocument, 0);
	},

	_adjustIds: function (element, idIndex) {
		if (element instanceof Element) {
			var eid = element.getAttributeNS(null, 'id');
			if (eid && eid !== "") {
				element.setAttributeNS(null, 'id', this.id + eid);
			} else {
				element.setAttributeNS(null, 'id', this.id + "_" + this.id + "_" + idIndex);
				idIndex++;
			}

			// Replace URL in fill attribute
			var fill = element.getAttributeNS(null, 'fill');
			if (fill && fill.include("url(#")) {
				fill = fill.replace(/url\(#/g, 'url(#' + this.id);
				element.setAttributeNS(null, 'fill', fill);
			}

			if (element.hasChildNodes()) {
				for (var i = 0; i < element.childNodes.length; i++) {
					idIndex = this._adjustIds(element.childNodes[i], idIndex);
				}
			}
		}
		return idIndex;
	},

	toString: function () { return "ORYX.Core.Shape " + this.getId() }
};
ORYX.Core.Shape = ORYX.Core.AbstractShape.extend(ORYX.Core.Shape);/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.Controls) { ORYX.Core.Controls = {}; }


/**
 * @classDescription Abstract base class for all Controls.
 */
ORYX.Core.Controls.Control = ORYX.Core.UIObject.extend({

	toString: function () { return "Control " + this.id; }
});

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/


/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.Controls) { ORYX.Core.Controls = {}; }


/**
 * @classDescription Represents a movable docker that can be bound to a shape. Dockers are used
 * for positioning shape objects.
 * @extends {Control}
 * 
 * TODO absoluteXY und absoluteCenterXY von einem Docker liefern falsche Werte!!!
 */
ORYX.Core.Controls.Docker = ORYX.Core.Controls.Control.extend({
	/**
	 * Constructor
	 */
	construct: function () {
		arguments.callee.$.construct.apply(this, arguments);

		this.isMovable = true;				// Enables movability
		this.bounds.set(0, 0, 16, 16);		// Set the bounds
		this.referencePoint = undefined;		// Refrenzpoint 
		this._dockedShapeBounds = undefined;
		this._dockedShape = undefined;
		this._oldRefPoint1 = undefined;
		this._oldRefPoint2 = undefined;

		//this.anchors = [];
		this.anchorLeft;
		this.anchorRight;
		this.anchorTop;
		this.anchorBottom;

		this.node = ORYX.Editor.graft("http://www.w3.org/2000/svg",
			null,
			['g']);

		// The DockerNode reprasentation
		this._dockerNode = ORYX.Editor.graft("http://www.w3.org/2000/svg",
			this.node,
			['g', { "pointer-events": "all" },
				['circle', { cx: "8", cy: "8", r: "8", stroke: "none", fill: "none" }],
				['circle', { cx: "8", cy: "8", r: "3", stroke: "black", fill: "red", "stroke-width": "1" }]
			]);

		// The ReferenzNode reprasentation	
		this._referencePointNode = ORYX.Editor.graft("http://www.w3.org/2000/svg",
			this.node,
			['g', { "pointer-events": "none" },
				['circle', { cx: this.bounds.upperLeft().x, cy: this.bounds.upperLeft().y, r: 3, fill: "red", "fill-opacity": 0.4 }]]);

		// Hide the Docker
		this.hide();

		//Add to the EventHandler
		this.addEventHandlers(this._dockerNode);

		// Buffer the Update Callback for un-/register on Event-Handler 
		this._updateCallback = this._changed.bind(this);
	},

	update: function () {
		// If there have an DockedShape	
		if (this._dockedShape) {
			if (this._dockedShapeBounds && this._dockedShape instanceof ORYX.Core.Node) {
				// Calc the delta of width and height of the lastBounds and the current Bounds
				var dswidth = this._dockedShapeBounds.width();
				var dsheight = this._dockedShapeBounds.height();
				if (!dswidth)
					dswidth = 1;
				if (!dsheight)
					dsheight = 1;
				var widthDelta = this._dockedShape.bounds.width() / dswidth;
				var heightDelta = this._dockedShape.bounds.height() / dsheight;

				// If there is an different
				if (widthDelta !== 1.0 || heightDelta !== 1.0) {
					// Set the delta
					this.referencePoint.x *= widthDelta;
					this.referencePoint.y *= heightDelta;
				}

				// Clone these bounds
				this._dockedShapeBounds = this._dockedShape.bounds.clone();
			}

			// Get the first and the last Docker of the parent Shape
			var dockerIndex = this.parent.dockers.indexOf(this)
			var dock1 = this;
			var dock2 = this.parent.dockers.length > 1 ?
				(dockerIndex === 0 ?							// If there is the first element
					this.parent.dockers[dockerIndex + 1] :	// then take the next docker
					this.parent.dockers[dockerIndex - 1]) :  // if not, then take the docker before
				undefined;

			// Calculate the first absolute Refenzpoint 
			var absoluteReferenzPoint1 = dock1.getDockedShape() ?
				dock1.getAbsoluteReferencePoint() :
				dock1.bounds.center();

			// Calculate the last absolute Refenzpoint 
			var absoluteReferenzPoint2 = dock2 && dock2.getDockedShape() ?
				dock2.getAbsoluteReferencePoint() :
				dock2 ?
					dock2.bounds.center() :
					undefined;

			// If there is no last absolute Referenzpoint		
			if (!absoluteReferenzPoint2) {
				// Calculate from the middle of the DockedShape
				var center = this._dockedShape.absoluteCenterXY();
				var minDimension = this._dockedShape.bounds.width() * this._dockedShape.bounds.height();
				absoluteReferenzPoint2 = {
					x: absoluteReferenzPoint1.x + (center.x - absoluteReferenzPoint1.x) * -minDimension,
					y: absoluteReferenzPoint1.y + (center.y - absoluteReferenzPoint1.y) * -minDimension
				}
			}

			var newPoint = undefined;

			/*if (!this._oldRefPoint1 || !this._oldRefPoint2 ||
				absoluteReferenzPoint1.x !== this._oldRefPoint1.x ||
				absoluteReferenzPoint1.y !== this._oldRefPoint1.y ||
				absoluteReferenzPoint2.x !== this._oldRefPoint2.x ||
				absoluteReferenzPoint2.y !== this._oldRefPoint2.y) {*/

			// Get the new point for the Docker, calucalted by the intersection point of the Shape and the two points
			newPoint = this._dockedShape.getIntersectionPoint(absoluteReferenzPoint1, absoluteReferenzPoint2);

			// If there is new point, take the referencepoint as the new point
			if (!newPoint) {
				newPoint = this.getAbsoluteReferencePoint();
			}

			if (this.parent && this.parent.parent) {
				var grandParentPos = this.parent.parent.absoluteXY();
				newPoint.x -= grandParentPos.x;
				newPoint.y -= grandParentPos.y;
			}

			// Set the bounds to the new point
			this.bounds.centerMoveTo(newPoint)

			this._oldRefPoint1 = absoluteReferenzPoint1;
			this._oldRefPoint2 = absoluteReferenzPoint2;
		}
		/*else {
			newPoint = this.bounds.center();
		}*/


		//	}

		// Call the super class
		arguments.callee.$.update.apply(this, arguments);
	},

	/**
	 * Calls the super class refresh method and updates the view of the docker.
	 */
	refresh: function () {
		arguments.callee.$.refresh.apply(this, arguments);

		// Refresh the dockers node
		var p = this.bounds.upperLeft();
		this._dockerNode.setAttributeNS(null, 'transform', 'translate(' + p.x + ', ' + p.y + ')');

		// Refresh the referencepoints node
		p = Object.clone(this.referencePoint);

		if (p && this._dockedShape) {
			var upL
			if (this.parent instanceof ORYX.Core.Edge) {
				upL = this._dockedShape.absoluteXY();
			} else {
				upL = this._dockedShape.bounds.upperLeft();
			}
			p.x += upL.x;
			p.y += upL.y;
		} else {
			p = this.bounds.center();
		}

		this._referencePointNode.setAttributeNS(null, 'transform', 'translate(' + p.x + ', ' + p.y + ')');
	},

	/**
	 * Set the reference point
	 * @param {Object} point
	 */
	setReferencePoint: function (point) {
		// Set the referencepoint
		if (this.referencePoint !== point &&
			(!this.referencePoint ||
				!point ||
				this.referencePoint.x !== point.x ||
				this.referencePoint.y !== point.y)) {

			this.referencePoint = point;
			this._changed();
		}


		// Update directly, because the referencepoint has no influence of the bounds
		//this.refresh();
	},

	/**
	 * Get the absolute referencepoint
	 */
	getAbsoluteReferencePoint: function () {
		if (!this.referencePoint || !this._dockedShape) {
			return undefined;
		} else {
			var absUL = this._dockedShape.absoluteXY();
			return {
				x: this.referencePoint.x + absUL.x,
				y: this.referencePoint.y + absUL.y
			}
		}
	},

	/**
	 * Set the docked Shape from the docker
	 * @param {Object} shape
	 */
	setDockedShape: function (shape) {

		// If there is an old docked Shape
		if (this._dockedShape) {
			this._dockedShape.bounds.unregisterCallback(this._updateCallback)

			// Delete the Shapes from the incoming and outgoing array
			// If this Docker the incoming of the Shape
			if (this === this.parent.dockers.first()) {

				this.parent.incoming = this.parent.incoming.without(this._dockedShape);
				this._dockedShape.outgoing = this._dockedShape.outgoing.without(this.parent);

				// If this Docker the outgoing of the Shape	
			} else if (this === this.parent.dockers.last()) {

				this.parent.outgoing = this.parent.outgoing.without(this._dockedShape);
				this._dockedShape.incoming = this._dockedShape.incoming.without(this.parent);

			}

		}


		// Set the new Shape
		this._dockedShape = shape;
		this._dockedShapeBounds = undefined;
		var referencePoint = undefined;

		// If there is an Shape, register the updateCallback if there are changes in the shape bounds
		if (this._dockedShape) {

			// Add the Shapes to the incoming and outgoing array
			// If this Docker the incoming of the Shape
			if (this === this.parent.dockers.first()) {

				this.parent.incoming.push(shape);
				shape.outgoing.push(this.parent);

				// If this Docker the outgoing of the Shape	
			} else if (this === this.parent.dockers.last()) {

				this.parent.outgoing.push(shape);
				shape.incoming.push(this.parent);

			}

			// Get the bounds and set the new referencepoint
			var bounds = this.bounds;
			var absUL = shape.absoluteXY();

			/*if(shape.parent){
				var b = shape.parent.bounds.upperLeft();
				absUL.x -= b.x;
				absUL.y -= b.y;
			}*/

			referencePoint = {
				x: bounds.center().x - absUL.x,
				y: bounds.center().y - absUL.y
			}

			this._dockedShapeBounds = this._dockedShape.bounds.clone();

			this._dockedShape.bounds.registerCallback(this._updateCallback);

			// Set the color of the docker as docked
			this.setDockerColor(ORYX.CONFIG.DOCKER_DOCKED_COLOR);
		} else {
			// Set the color of the docker as undocked
			this.setDockerColor(ORYX.CONFIG.DOCKER_UNDOCKED_COLOR);
		}

		// Set the referencepoint
		this.setReferencePoint(referencePoint);
		this._changed();
		//this.update();
	},

	/**
	 * Get the docked Shape
	 */
	getDockedShape: function () {
		return this._dockedShape;
	},

	/**
	 * Returns TRUE if the docker has a docked shape
	 */
	isDocked: function () {
		return !!this._dockedShape;
	},

	/**
	 * Set the Color of the Docker
	 * @param {Object} color
	 */
	setDockerColor: function (color) {
		this._dockerNode.lastChild.setAttributeNS(null, "fill", color);
	},

	preventHiding: function (prevent) {
		this._preventHiding = Math.max(0, (this._preventHiding || 0) + (prevent ? 1 : -1));
	},

	/**
	 * Hides this UIObject and all its children.
	 */
	hide: function () {
		if (this._preventHiding) {
			return false;
		}

		// Hide docker and reference point
		this.node.setAttributeNS(null, 'visibility', 'hidden');
		this._referencePointNode.setAttributeNS(null, 'visibility', 'hidden');

		this.children.each(function (uiObj) {
			uiObj.hide();
		});
	},

	/**
	 * Enables visibility of this UIObject and all its children.
	 */
	show: function () {
		// Show docker
		this.node.setAttributeNS(null, 'visibility', 'visible');

		// Hide reference point if the connected shape is an edge
		if (this.getDockedShape() instanceof ORYX.Core.Edge) {
			this._referencePointNode.setAttributeNS(null, 'visibility', 'hidden');
		} else {
			this._referencePointNode.setAttributeNS(null, 'visibility', 'visible');
		}

		this.children.each(function (uiObj) {
			uiObj.show();
		});
	},

	toString: function () { return "Docker " + this.id }
});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) { var ORYX = {}; }
if (!ORYX.Core) { ORYX.Core = {}; }
if (!ORYX.Core.Controls) { ORYX.Core.Controls = {}; }


/**
 * @classDescription Represents a magnet that is part of another shape and can
 * be attached to dockers. Magnets are used for linking edge objects
 * to other Shape objects.
 * @extends {Control}
 */
ORYX.Core.Controls.Magnet = ORYX.Core.Controls.Control.extend({

	/**
	 * Constructor
	 */
	construct: function () {
		arguments.callee.$.construct.apply(this, arguments);

		//this.anchors = [];
		this.anchorLeft;
		this.anchorRight;
		this.anchorTop;
		this.anchorBottom;

		this.bounds.set(0, 0, 16, 16);

		//graft magnet's root node into owner's control group.
		this.node = ORYX.Editor.graft("http://www.w3.org/2000/svg",
			null,
			['g', { "pointer-events": "all" },
				['circle', { cx: "8", cy: "8", r: "4", stroke: "none", fill: "red", "fill-opacity": "0.3" }],
			]);

		this.hide();
	},

	update: function () {
		arguments.callee.$.update.apply(this, arguments);

		//this.isChanged = true;
	},

	_update: function () {
		arguments.callee.$.update.apply(this, arguments);

		//this.isChanged = true;
	},

	refresh: function () {
		arguments.callee.$.refresh.apply(this, arguments);

		var p = this.bounds.upperLeft();
		/*if(this.parent) {
			var parentPos = this.parent.bounds.upperLeft();
			p.x += parentPos.x;
			p.y += parentPos.y;
		}*/

		this.node.setAttributeNS(null, 'transform', 'translate(' + p.x + ', ' + p.y + ')');
	},

	show: function () {
		//this.refresh();
		arguments.callee.$.show.apply(this, arguments);
	},

	toString: function () {
		return "Magnet " + this.id;
	}
});
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * Init namespaces
 */
if (!ORYX) {
	var ORYX = {};
}
if (!ORYX.Core) {
	ORYX.Core = {};
}

/**
 * @classDescription Abstract base class for all Nodes.
 * @extends ORYX.Core.Shape
 */
ORYX.Core.Node = {

    /**
     * Constructor
     * @param options {Object} A container for arguments.
     * @param stencil {Stencil}
     */
	construct: function (options, stencil) {
		arguments.callee.$.construct.apply(this, arguments);

		this.isSelectable = true;
		this.isMovable = true;
		this._dockerUpdated = false;

		this._oldBounds = new ORYX.Core.Bounds(); //init bounds with undefined values
		this._svgShapes = []; //array of all SVGShape objects of
		// SVG representation

		//TODO vielleicht in shape verschieben?
		this.minimumSize = undefined; // {width:..., height:...}
		this.maximumSize = undefined;

		//TODO vielleicht in shape oder uiobject verschieben?
		// vielleicht sogar isResizable ersetzen?
		this.isHorizontallyResizable = false;
		this.isVerticallyResizable = false;

		this.dataId = undefined;

		this._init(this._stencil.view());
	},

    /**
     * This method checks whether the shape is resized correctly and calls the
     * super class update method.
     */
	_update: function () {

		this.dockers.invoke("update");
		if (this.isChanged) {

			var bounds = this.bounds;
			var oldBounds = this._oldBounds;

			if (this.isResized) {

				var widthDelta = bounds.width() / oldBounds.width();
				var heightDelta = bounds.height() / oldBounds.height();

				//iterate over all relevant svg elements and resize them
				this._svgShapes.each(function (svgShape) {
					//adjust width
					if (svgShape.isHorizontallyResizable) {
						svgShape.width = svgShape.oldWidth * widthDelta;
					}
					//adjust height
					if (svgShape.isVerticallyResizable) {
						svgShape.height = svgShape.oldHeight * heightDelta;
					}

					//check, if anchors are set
					var anchorOffset;
					var leftIncluded = svgShape.anchorLeft;
					var rightIncluded = svgShape.anchorRight;

					if (rightIncluded) {
						anchorOffset = oldBounds.width() - (svgShape.oldX + svgShape.oldWidth);
						if (leftIncluded) {
							svgShape.width = bounds.width() - svgShape.x - anchorOffset;
						}
						else {
							svgShape.x = bounds.width() - (anchorOffset + svgShape.width);
						}
					}
					else
						if (!leftIncluded) {
							svgShape.x = widthDelta * svgShape.oldX;
							if (!svgShape.isHorizontallyResizable) {
								svgShape.x = svgShape.x + svgShape.width * widthDelta / 2 - svgShape.width / 2;
							}
						}

					var topIncluded = svgShape.anchorTop;
					var bottomIncluded = svgShape.anchorBottom;

					if (bottomIncluded) {
						anchorOffset = oldBounds.height() - (svgShape.oldY + svgShape.oldHeight);
						if (topIncluded) {
							svgShape.height = bounds.height() - svgShape.y - anchorOffset;
						}
						else {
							// Hack for choreography task layouting
							if (!svgShape._isYLocked) {
								svgShape.y = bounds.height() - (anchorOffset + svgShape.height);
							}
						}
					}
					else
						if (!topIncluded) {
							svgShape.y = heightDelta * svgShape.oldY;
							if (!svgShape.isVerticallyResizable) {
								svgShape.y = svgShape.y + svgShape.height * heightDelta / 2 - svgShape.height / 2;
							}
						}
				});

				//check, if the current bounds is unallowed horizontally or vertically resized
				var p = {
					x: 0,
					y: 0
				};
				if (!this.isHorizontallyResizable && bounds.width() !== oldBounds.width()) {
					p.x = oldBounds.width() - bounds.width();
				}
				if (!this.isVerticallyResizable && bounds.height() !== oldBounds.height()) {
					p.y = oldBounds.height() - bounds.height();
				}
				if (p.x !== 0 || p.y !== 0) {
					bounds.extend(p);
				}

				//check, if the current bounds are between maximum and minimum bounds
				p = {
					x: 0,
					y: 0
				};
				var widthDifference, heightDifference;
				if (this.minimumSize) {

					ORYX.Log.debug("Shape (%0)'s min size: (%1x%2)", this, this.minimumSize.width, this.minimumSize.height);
					widthDifference = this.minimumSize.width - bounds.width();
					if (widthDifference > 0) {
						p.x += widthDifference;
					}
					heightDifference = this.minimumSize.height - bounds.height();
					if (heightDifference > 0) {
						p.y += heightDifference;
					}
				}
				if (this.maximumSize) {

					ORYX.Log.debug("Shape (%0)'s max size: (%1x%2)", this, this.maximumSize.width, this.maximumSize.height);
					widthDifference = bounds.width() - this.maximumSize.width;
					if (widthDifference > 0) {
						p.x -= widthDifference;
					}
					heightDifference = bounds.height() - this.maximumSize.height;
					if (heightDifference > 0) {
						p.y -= heightDifference;
					}
				}
				if (p.x !== 0 || p.y !== 0) {
					bounds.extend(p);
				}

				//update magnets

				var widthDelta = bounds.width() / oldBounds.width();
				var heightDelta = bounds.height() / oldBounds.height();

				var leftIncluded, rightIncluded, topIncluded, bottomIncluded, center, newX, newY;

				this.magnets.each(function (magnet) {
					leftIncluded = magnet.anchorLeft;
					rightIncluded = magnet.anchorRight;
					topIncluded = magnet.anchorTop;
					bottomIncluded = magnet.anchorBottom;

					center = magnet.bounds.center();

					if (leftIncluded) {
						newX = center.x;
					}
					else
						if (rightIncluded) {
							newX = bounds.width() - (oldBounds.width() - center.x)
						}
						else {
							newX = center.x * widthDelta;
						}

					if (topIncluded) {
						newY = center.y;
					}
					else
						if (bottomIncluded) {
							newY = bounds.height() - (oldBounds.height() - center.y);
						}
						else {
							newY = center.y * heightDelta;
						}

					if (center.x !== newX || center.y !== newY) {
						magnet.bounds.centerMoveTo(newX, newY);
					}
				});

				//set new position of labels
				this.getLabels().each(function (label) {
					// Set the position dependings on it anchor
					if (!label.isAnchorLeft()) {
						if (label.isAnchorRight()) {
							label.setX(bounds.width() - (oldBounds.width() - label.oldX))
						} else {
							label.setX((label.position ? label.position.x : label.x) * widthDelta);
						}
					}
					if (!label.isAnchorTop()) {
						if (label.isAnchorBottom()) {
							label.setY(bounds.height() - (oldBounds.height() - label.oldY));
						} else {
							label.setY((label.position ? label.position.y : label.y) * heightDelta);
						}
					}

					// If there is an position,
					// set the origin position as well
					if (label.position) {
						if (!label.isOriginAnchorLeft()) {
							if (label.isOriginAnchorRight()) {
								label.setOriginX(bounds.width() - (oldBounds.width() - label.oldX))
							} else {
								label.setOriginX(label.x * widthDelta);
							}
						}
						if (!label.isOriginAnchorTop()) {
							if (label.isOriginAnchorBottom()) {
								label.setOriginY(bounds.height() - (oldBounds.height() - label.oldY));
							} else {
								label.setOriginY(label.y * heightDelta);
							}
						}
					}
				});

				//update docker
				var docker = this.dockers[0];
				if (docker) {
					docker.bounds.unregisterCallback(this._dockerChangedCallback);
					if (!this._dockerUpdated) {
						docker.bounds.centerMoveTo(this.bounds.center());
						this._dockerUpdated = false;
					}

					docker.update();
					docker.bounds.registerCallback(this._dockerChangedCallback);
				}
				this.isResized = false;
			}

			this.refresh();

			this.isChanged = false;

			this._oldBounds = this.bounds.clone();
		}

		this.children.each(function (value) {
			if (!(value instanceof ORYX.Core.Controls.Docker)) {
				value._update();
			}
		});

		if (this.dockers.length > 0 && !this.dockers.first().getDockedShape()) {
			this.dockers.each(function (docker) {
				docker.bounds.centerMoveTo(this.bounds.center())
			}.bind(this))
		}

		/*this.incoming.each((function(edge) {
			if(!(this.dockers[0] && this.dockers[0].getDockedShape() instanceof ORYX.Core.Node))
				edge._update(true);
		}).bind(this));
		
		this.outgoing.each((function(edge) {
			if(!(this.dockers[0] && this.dockers[0].getDockedShape() instanceof ORYX.Core.Node))
				edge._update(true);
		}).bind(this)); */
	},

    /**
     * This method repositions and resizes the SVG representation
     * of the shape.
     */
	refresh: function () {
		arguments.callee.$.refresh.apply(this, arguments);

		/** Movement */
		var x = this.bounds.upperLeft().x;
		var y = this.bounds.upperLeft().y;

		//set translation in transform attribute
        /*var attributeTransform = document.createAttributeNS(ORYX.CONFIG.NAMESPACE_SVG, "transform");
        attributeTransform.nodeValue = "translate(" + x + ", " + y + ")";
        this.node.firstChild.setAttributeNode(attributeTransform);*/
		// Move owner element
		this.node.firstChild.setAttributeNS(null, "transform", "translate(" + x + ", " + y + ")");
		// Move magnets
		this.node.childNodes[1].childNodes[1].setAttributeNS(null, "transform", "translate(" + x + ", " + y + ")");

		/** Resize */

		//iterate over all relevant svg elements and update them
		this._svgShapes.each(function (svgShape) {
			svgShape.update();
		});
	},

	_dockerChanged: function () {
		var docker = this.dockers[0];

		//set the bounds of the the association
		this.bounds.centerMoveTo(docker.bounds.center());

		this._dockerUpdated = true;
		//this._update(true);
	},

    /**
     * This method traverses a tree of SVGElements and returns
     * all SVGShape objects. For each basic shape or path element
     * a SVGShape object is initialized.
     *
     * @param svgNode {SVGElement}
     * @return {Array} Array of SVGShape objects
     */
	_initSVGShapes: function (svgNode) {
		var svgShapes = [];
		try {
			var svgShape = new ORYX.Core.SVG.SVGShape(svgNode);
			svgShapes.push(svgShape);
		}
		catch (e) {
			//do nothing
		}

		if (svgNode.hasChildNodes()) {
			for (var i = 0; i < svgNode.childNodes.length; i++) {
				svgShapes = svgShapes.concat(this._initSVGShapes(svgNode.childNodes[i]));
			}
		}

		return svgShapes;
	},

    /**
     * Calculate if the point is inside the Shape
     * @param {PointX}
     * @param {PointY} 
     * @param {absoluteBounds} optional: for performance
     */
	isPointIncluded: function (pointX, pointY, absoluteBounds) {
		// If there is an arguments with the absoluteBounds
		var absBounds = absoluteBounds && absoluteBounds instanceof ORYX.Core.Bounds ? absoluteBounds : this.absoluteBounds();

		if (!absBounds.isIncluded(pointX, pointY)) {
			return false;
		} else {

		}


		//point = Object.clone(point);
		var ul = absBounds.upperLeft();
		var x = pointX - ul.x;
		var y = pointY - ul.y;

		var i = 0;
		do {
			var isPointIncluded = this._svgShapes[i++].isPointIncluded(x, y);
		} while (!isPointIncluded && i < this._svgShapes.length)

		return isPointIncluded;

        /*return this._svgShapes.any(function(svgShape){
            return svgShape.isPointIncluded(point);
        });*/
	},


    /**
     * Calculate if the point is over an special offset area
     * @param {Point}
     */
	isPointOverOffset: function (pointX, pointY) {
		var isOverEl = arguments.callee.$.isPointOverOffset.apply(this, arguments);

		if (isOverEl) {

			// If there is an arguments with the absoluteBounds
			var absBounds = this.absoluteBounds();
			absBounds.widen(- ORYX.CONFIG.BORDER_OFFSET);

			if (!absBounds.isIncluded(pointX, pointY)) {
				return true;
			}
		}

		return false;

	},

	serialize: function () {
		var result = arguments.callee.$.serialize.apply(this);

		// Add the docker's bounds
		// nodes only have at most one docker!
		this.dockers.each((function (docker) {
			if (docker.getDockedShape()) {
				var center = docker.referencePoint;
				center = center ? center : docker.bounds.center();
				result.push({
					name: 'docker',
					prefix: 'oryx',
					value: $H(center).values().join(','),
					type: 'literal'
				});
			}
		}).bind(this));

		// Get the spezific serialized object from the stencil
		try {
			//result = this.getStencil().serialize(this, result);

			var serializeEvent = this.getStencil().serialize();

			/*
			 * call serialize callback by reference, result should be found
			 * in serializeEvent.result
			 */
			if (serializeEvent.type) {
				serializeEvent.shape = this;
				serializeEvent.data = result;
				serializeEvent.result = undefined;
				serializeEvent.forceExecution = true;

				this._delegateEvent(serializeEvent);

				if (serializeEvent.result) {
					result = serializeEvent.result;
				}
			}
		}
		catch (e) {
		}
		return result;
	},

	deserialize: function (data) {
		arguments.callee.$.deserialize.apply(this, arguments);

		try {
			//data = this.getStencil().deserialize(this, data);

			var deserializeEvent = this.getStencil().deserialize();

			/*
			 * call serialize callback by reference, result should be found
			 * in serializeEventInfo.result
			 */
			if (deserializeEvent.type) {
				deserializeEvent.shape = this;
				deserializeEvent.data = data;
				deserializeEvent.result = undefined;
				deserializeEvent.forceExecution = true;

				this._delegateEvent(deserializeEvent);
				if (deserializeEvent.result) {
					data = deserializeEvent.result;
				}
			}
		}
		catch (e) {
		}

		// Set the outgoing shapes
		var outgoing = data.findAll(function (ser) { return (ser.prefix + "-" + ser.name) == 'raziel-outgoing' });
		outgoing.each((function (obj) {
			// TODO: Look at Canvas
			if (!this.parent) { return };

			// Set outgoing Shape
			var next = this.getCanvas().getChildShapeByResourceId(obj.value);

			if (next) {
				if (next instanceof ORYX.Core.Edge) {
					//Set the first docker of the next shape
					next.dockers.first().setDockedShape(this);
					next.dockers.first().setReferencePoint(next.dockers.first().bounds.center());
				} else if (next.dockers.length > 0) { //next is a node and next has a docker
					next.dockers.first().setDockedShape(this);
					//next.dockers.first().setReferencePoint({x: this.bounds.width() / 2.0, y: this.bounds.height() / 2.0});
				}
			}

		}).bind(this));

		if (this.dockers.length === 1) {
			var dockerPos;
			dockerPos = data.find(function (entry) {
				return (entry.prefix + "-" + entry.name === "oryx-dockers");
			});

			if (dockerPos) {
				var points = dockerPos.value.replace(/,/g, " ").split(" ").without("").without("#");
				if (points.length === 2 && this.dockers[0].getDockedShape()) {
					this.dockers[0].setReferencePoint({
						x: parseFloat(points[0]),
						y: parseFloat(points[1])
					});
				}
				else {
					this.dockers[0].bounds.centerMoveTo(parseFloat(points[0]), parseFloat(points[1]));
				}
			}
		}
	},

    /**
     * This method excepts the SVGDoucment that is the SVG representation
     * of this shape.
     * The bounds of the shape are calculated, the SVG representation's upper left point
     * is moved to 0,0 and it the method sets if this shape is resizable.
     *
     * @param {SVGDocument} svgDocument
     */
	_init: function (svgDocument) {
		arguments.callee.$._init.apply(this, arguments);

		var svgNode = svgDocument.getElementsByTagName("g")[0]; //outer most g node
		// set all required attributes

		var attributeTitle = svgDocument.ownerDocument.createAttribute("title");
		attributeTitle.nodeValue = this.getStencil().title();
		svgNode.setAttributeNode(attributeTitle);

		var attributeId = svgDocument.ownerDocument.createAttribute("id");
		attributeId.nodeValue = this.id;
		svgNode.setAttributeNode(attributeId);

		// 
		var stencilTargetNode = this.node.childNodes[0].childNodes[0]; //<g class=me>"
		svgNode = stencilTargetNode.appendChild(svgNode);

		// Add to the EventHandler
		this.addEventHandlers(svgNode.parentNode);

		/**set minimum and maximum size*/
		var minSizeAttr = svgNode.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "minimumSize");
		if (minSizeAttr) {
			minSizeAttr = minSizeAttr.replace("/,/g", " ");
			var minSizeValues = minSizeAttr.split(" ");
			minSizeValues = minSizeValues.without("");

			if (minSizeValues.length > 1) {
				this.minimumSize = {
					width: parseFloat(minSizeValues[0]),
					height: parseFloat(minSizeValues[1])
				};
			}
			else {
				//set minimumSize to (1,1), so that width and height of the stencil can never be (0,0)
				this.minimumSize = {
					width: 1,
					height: 1
				};
			}
		}

		var maxSizeAttr = svgNode.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "maximumSize");
		if (maxSizeAttr) {
			maxSizeAttr = maxSizeAttr.replace("/,/g", " ");
			var maxSizeValues = maxSizeAttr.split(" ");
			maxSizeValues = maxSizeValues.without("");

			if (maxSizeValues.length > 1) {
				this.maximumSize = {
					width: parseFloat(maxSizeValues[0]),
					height: parseFloat(maxSizeValues[1])
				};
			}
		}

		if (this.minimumSize && this.maximumSize &&
			(this.minimumSize.width > this.maximumSize.width ||
				this.minimumSize.height > this.maximumSize.height)) {

			//TODO wird verschluckt!!!
			throw this + ": Minimum Size must be greater than maxiumSize.";
		}

		/**get current bounds and adjust it to upperLeft == (0,0)*/
		//initialize all SVGShape objects
		this._svgShapes = this._initSVGShapes(svgNode);

		//get upperLeft and lowerRight of stencil
		var upperLeft = {
			x: undefined,
			y: undefined
		};
		var lowerRight = {
			x: undefined,
			y: undefined
		};
		var me = this;
		this._svgShapes.each(function (svgShape) {
			upperLeft.x = (upperLeft.x !== undefined) ? Math.min(upperLeft.x, svgShape.x) : svgShape.x;
			upperLeft.y = (upperLeft.y !== undefined) ? Math.min(upperLeft.y, svgShape.y) : svgShape.y;
			lowerRight.x = (lowerRight.x !== undefined) ? Math.max(lowerRight.x, svgShape.x + svgShape.width) : svgShape.x + svgShape.width;
			lowerRight.y = (lowerRight.y !== undefined) ? Math.max(lowerRight.y, svgShape.y + svgShape.height) : svgShape.y + svgShape.height;

			/** set if resizing is enabled */
			//TODO isResizable durch die beiden anderen booleans ersetzen?
			if (svgShape.isHorizontallyResizable) {
				me.isHorizontallyResizable = true;
				me.isResizable = true;
			}
			if (svgShape.isVerticallyResizable) {
				me.isVerticallyResizable = true;
				me.isResizable = true;
			}
			if (svgShape.anchorTop && svgShape.anchorBottom) {
				me.isVerticallyResizable = true;
				me.isResizable = true;
			}
			if (svgShape.anchorLeft && svgShape.anchorRight) {
				me.isHorizontallyResizable = true;
				me.isResizable = true;
			}
		});

		//move all SVGShapes by -upperLeft
		this._svgShapes.each(function (svgShape) {
			svgShape.x -= upperLeft.x;
			svgShape.y -= upperLeft.y;
			svgShape.update();
		});

		//set bounds of shape
		//the offsets are also needed for positioning the magnets and the docker
		var offsetX = upperLeft.x;
		var offsetY = upperLeft.y;

		lowerRight.x -= offsetX;
		lowerRight.y -= offsetY;
		upperLeft.x = 0;
		upperLeft.y = 0;

		//prevent that width or height of initial bounds is 0
		if (lowerRight.x === 0) {
			lowerRight.x = 1;
		}
		if (lowerRight.y === 0) {
			lowerRight.y = 1;
		}

		this._oldBounds.set(upperLeft, lowerRight);
		this.bounds.set(upperLeft, lowerRight);

		/**initialize magnets */

		var magnets = svgDocument.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_ORYX, "magnets");

		if (magnets && magnets.length > 0) {

			magnets = $A(magnets[0].getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_ORYX, "magnet"));

			var me = this;
			magnets.each(function (magnetElem) {
				var magnet = new ORYX.Core.Controls.Magnet({
					eventHandlerCallback: me.eventHandlerCallback
				});
				var cx = parseFloat(magnetElem.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "cx"));
				var cy = parseFloat(magnetElem.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "cy"));
				magnet.bounds.centerMoveTo({
					x: cx - offsetX,
					y: cy - offsetY
				});

				//get anchors
				var anchors = magnetElem.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "anchors");
				if (anchors) {
					anchors = anchors.replace("/,/g", " ");
					anchors = anchors.split(" ").without("");
					for (var i = 0; i < anchors.length; i++) {
						switch (anchors[i].toLowerCase()) {
							case "left":
								magnet.anchorLeft = true;
								break;
							case "right":
								magnet.anchorRight = true;
								break;
							case "top":
								magnet.anchorTop = true;
								break;
							case "bottom":
								magnet.anchorBottom = true;
								break;
						}
					}
				}

				me.add(magnet);

				//check, if magnet is default magnet
				if (!this._defaultMagnet) {
					var defaultAttr = magnetElem.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "default");
					if (defaultAttr && defaultAttr.toLowerCase() === "yes") {
						me._defaultMagnet = magnet;
					}
				}
			});
		}
		else {
			// Add a Magnet in the Center of Shape			
			var magnet = new ORYX.Core.Controls.Magnet();
			magnet.bounds.centerMoveTo(this.bounds.width() / 2, this.bounds.height() / 2);
			this.add(magnet);
		}

		/**initialize docker */
		var dockerElem = svgDocument.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_ORYX, "docker");

		if (dockerElem && dockerElem.length > 0) {
			dockerElem = dockerElem[0];
			var docker = this.createDocker();
			var cx = parseFloat(dockerElem.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "cx"));
			var cy = parseFloat(dockerElem.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "cy"));
			docker.bounds.centerMoveTo({
				x: cx - offsetX,
				y: cy - offsetY
			});

			//get anchors
			var anchors = dockerElem.getAttributeNS(ORYX.CONFIG.NAMESPACE_ORYX, "anchors");
			if (anchors) {
				anchors = anchors.replace("/,/g", " ");
				anchors = anchors.split(" ").without("");

				for (var i = 0; i < anchors.length; i++) {
					switch (anchors[i].toLowerCase()) {
						case "left":
							docker.anchorLeft = true;
							break;
						case "right":
							docker.anchorRight = true;
							break;
						case "top":
							docker.anchorTop = true;
							break;
						case "bottom":
							docker.anchorBottom = true;
							break;
					}
				}
			}
		}

		/**initialize labels*/
		var textElems = svgNode.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'text');
		$A(textElems).each((function (textElem) {
			var label = new ORYX.Core.SVG.Label({
				textElement: textElem,
				shapeId: this.id
			});
			label.x -= offsetX;
			label.y -= offsetY;
			this._labels[label.id] = label;

			label.registerOnChange(this.layout.bind(this));

		}).bind(this));
	},

	/**
	 * Override the Method, that a docker is not shown
	 *
	 */
	createDocker: function () {
		var docker = new ORYX.Core.Controls.Docker({ eventHandlerCallback: this.eventHandlerCallback });
		docker.bounds.registerCallback(this._dockerChangedCallback);

		this.dockers.push(docker);
		docker.parent = this;
		docker.bounds.registerCallback(this._changedCallback);

		return docker
	},

	toString: function () {
		return this._stencil.title() + " " + this.id
	}
};
ORYX.Core.Node = ORYX.Core.Shape.extend(ORYX.Core.Node);
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
NAMESPACE_SVG = "http://www.w3.org/2000/svg";
NAMESPACE_ORYX = "http://www.b3mn.org/oryx";


/**
 * Init namespaces
 */
if (!ORYX) {
	var ORYX = {};
}
if (!ORYX.Core) {
	ORYX.Core = {};
}


/**
 * @classDescription Abstract base class for all connections.
 * @extends {ORYX.Core.Shape}
 * @param options {Object}
 *
 * TODO da die verschiebung der Edge nicht ueber eine
 *  translation gemacht wird, die sich auch auf alle kind UIObjects auswirkt,
 *  muessen die kinder hier beim verschieben speziell betrachtet werden.
 *  Das sollte ueberarbeitet werden.
 *
 */
ORYX.Core.Edge = {
    /**
     * Constructor
     * @param {Object} options
     * @param {Stencil} stencil
     */
	construct: function (options, stencil) {
		arguments.callee.$.construct.apply(this, arguments);

		this.isMovable = true;
		this.isSelectable = true;

		this._dockerUpdated = false;

		this._markers = new Hash(); //a hash map of SVGMarker objects where keys are the marker ids
		this._paths = [];
		this._interactionPaths = [];
		this._dockersByPath = new Hash();
		this._markersByPath = new Hash();

		/* Data structures to store positioning information of attached child nodes */
		this.attachedNodePositionData = new Hash();

		//TODO was muss hier initial erzeugt werden?
		var stencilNode = this.node.childNodes[0].childNodes[0];
		stencilNode = ORYX.Editor.graft("http://www.w3.org/2000/svg", stencilNode, ['g', {
			"pointer-events": "painted"
		}]);

		//Add to the EventHandler
		this.addEventHandlers(stencilNode.parentNode);


		this._oldBounds = this.bounds.clone();

		//load stencil
		this._init(this._stencil.view());

		if (stencil instanceof Array) {
			this.deserialize(stencil);
		}

	},

	_update: function (force) {
		if (this._dockerUpdated || this.isChanged || force) {

			this.dockers.invoke("update");

			if (false && (this.bounds.width() === 0 || this.bounds.height() === 0)) {
				var width = this.bounds.width();
				var height = this.bounds.height();
				this.bounds.extend({
					x: width === 0 ? 2 : 0,
					y: height === 0 ? 2 : 0
				});
				this.bounds.moveBy({
					x: width === 0 ? -1 : 0,
					y: height === 0 ? -1 : 0
				});

			}

			// TODO: Bounds muss abhaengig des Eltern-Shapes gesetzt werden
			var upL = this.bounds.upperLeft();
			var oldUpL = this._oldBounds.upperLeft();
			var oldWidth = this._oldBounds.width() === 0 ? this.bounds.width() : this._oldBounds.width();
			var oldHeight = this._oldBounds.height() === 0 ? this.bounds.height() : this._oldBounds.height();
			var diffX = upL.x - oldUpL.x;
			var diffY = upL.y - oldUpL.y;
			var diffWidth = (this.bounds.width() / oldWidth) || 1;
			var diffHeight = (this.bounds.height() / oldHeight) || 1;

			this.dockers.each((function (docker) {
				// Unregister on BoundsChangedCallback
				docker.bounds.unregisterCallback(this._dockerChangedCallback);

				// If there is any changes at the edge and is there is not an DockersUpdate
				// set the new bounds to the docker
				if (!this._dockerUpdated) {
					docker.bounds.moveBy(diffX, diffY);

					if (diffWidth !== 1 || diffHeight !== 1) {
						var relX = docker.bounds.upperLeft().x - upL.x;
						var relY = docker.bounds.upperLeft().y - upL.y;

						docker.bounds.moveTo(upL.x + relX * diffWidth, upL.y + relY * diffHeight);
					}
				}
				// Do Docker update and register on DockersBoundChange
				docker.update();
				docker.bounds.registerCallback(this._dockerChangedCallback);

			}).bind(this));

			if (this._dockerUpdated) {
				var a = this.dockers.first().bounds.center();
				var b = this.dockers.first().bounds.center();

				this.dockers.each((function (docker) {
					var center = docker.bounds.center();
					a.x = Math.min(a.x, center.x);
					a.y = Math.min(a.y, center.y);
					b.x = Math.max(b.x, center.x);
					b.y = Math.max(b.y, center.y);
				}).bind(this));

				//set the bounds of the the association
				this.bounds.set(Object.clone(a), Object.clone(b));
			}

			upL = this.bounds.upperLeft(); oldUpL = this._oldBounds.upperLeft();
			diffWidth = (this.bounds.width() / (oldWidth || this.bounds.width())); diffHeight = (this.bounds.height() / (oldHeight || this.bounds.height()));
			diffX = upL.x - oldUpL.x; diffY = upL.y - oldUpL.y;

			//reposition labels
			this.getLabels().each(function (label) {

				if (label.getReferencePoint()) {
					var ref = label.getReferencePoint();
					var from = ref.segment.from, to = ref.segment.to;
					if (!from || !from.parent || !to || !to.parent) {
						return;
					}

					var fromPosition = from.bounds.center(), toPosition = to.bounds.center();

					if (fromPosition.x === ref.segment.fromPosition.x && fromPosition.y === ref.segment.fromPosition.y &&
						toPosition.x === ref.segment.toPosition.x && toPosition.y === ref.segment.toPosition.y && !ref.dirty) {
						return;
					}

					if (!this.parent.initializingShapes) {
						var oldDistance = ORYX.Core.Math.getDistanceBetweenTwoPoints(ref.segment.fromPosition, ref.segment.toPosition, ref.intersection);
						var newIntersection = ORYX.Core.Math.getPointBetweenTwoPoints(fromPosition, toPosition, isNaN(oldDistance) ? 0.5 : oldDistance);

						/**
						 * Set position
						 */
						// Get the orthogonal identity vector of the current segment
						var oiv = ORYX.Core.Math.getOrthogonalIdentityVector(fromPosition, toPosition);
						var isHor = Math.abs(oiv.y) === 1, isVer = Math.abs(oiv.x) === 1;
						oiv.x *= ref.distance; oiv.y *= ref.distance; 				// vector * distance
						oiv.x += newIntersection.x; oiv.y += newIntersection.y; 	// vector + the intersection point				
						var mx = isHor && ref.orientation && (ref.iorientation || ref.orientation).endsWith("r") ? -label.getWidth() : 0;
						var my = isVer && ref.orientation && (ref.iorientation || ref.orientation).startsWith("l") ? -label.getHeight() + 2 : 0;
						label.setX(oiv.x + mx); label.setY(oiv.y + my);

						// Update the reference point
						this.updateReferencePointOfLabel(label, newIntersection, from, to);
					} else {
						var oiv = ORYX.Core.Math.getOrthogonalIdentityVector(fromPosition, toPosition);
						oiv.x *= ref.distance; oiv.y *= ref.distance; // vector * distance
						oiv.x += ref.intersection.x; oiv.y += ref.intersection.y; // vector + the intersection point		
						label.setX(oiv.x); label.setY(oiv.y);
						ref.segment.fromPosition = fromPosition; ref.segment.toPosition = toPosition;
					}

					return;
				}

				// Update label position if no reference point is set
				if (label.position && !this.parent.initializingShapes) {
					var x = label.position.x + (diffX * (diffWidth || 1));
					if (x > this.bounds.lowerRight().x) {
						x += this.bounds.width() - (this.bounds.width() / (diffWidth || 1));
					}

					var y = label.position.y + (diffY * (diffHeight || 1));
					if (y > this.bounds.lowerRight().y) {
						y += this.bounds.height() - (this.bounds.height() / (diffHeight || 1));
					}
					label.setX(x); label.setY(y);
					return;
				}

				switch (label.getEdgePosition()) {
					case "starttop":
						var angle = this._getAngle(this.dockers[0], this.dockers[1]);
						var pos = this.dockers.first().bounds.center();

						if (angle <= 90 || angle > 270) {
							label.horizontalAlign("left");
							label.verticalAlign("bottom");
							label.x = pos.x + label.getOffsetTop();
							label.y = pos.y - label.getOffsetTop();
							label.rotate(360 - angle, pos);
						} else {
							label.horizontalAlign("right");
							label.verticalAlign("bottom");
							label.x = pos.x - label.getOffsetTop();
							label.y = pos.y - label.getOffsetTop();
							label.rotate(180 - angle, pos);
						}

						break;

					case "startmiddle":
						var angle = this._getAngle(this.dockers[0], this.dockers[1]);
						var pos = this.dockers.first().bounds.center();

						if (angle <= 90 || angle > 270) {
							label.horizontalAlign("left");
							label.verticalAlign("bottom");
							label.x = pos.x + 2;
							label.y = pos.y + 4;
							label.rotate(360 - angle, pos);
						} else {
							label.horizontalAlign("right");
							label.verticalAlign("bottom");
							label.x = pos.x + 1;
							label.y = pos.y + 4;
							label.rotate(180 - angle, pos);
						}

						break;

					case "startbottom":
						var angle = this._getAngle(this.dockers[0], this.dockers[1]);
						var pos = this.dockers.first().bounds.center();

						if (angle <= 90 || angle > 270) {
							label.horizontalAlign("left");
							label.verticalAlign("top");
							label.x = pos.x + label.getOffsetBottom();
							label.y = pos.y + label.getOffsetBottom();
							label.rotate(360 - angle, pos);
						} else {
							label.horizontalAlign("right");
							label.verticalAlign("top");
							label.x = pos.x - label.getOffsetBottom();
							label.y = pos.y + label.getOffsetBottom();
							label.rotate(180 - angle, pos);
						}

						break;
					case "midtop":
						var numOfDockers = this.dockers.length;
						if (numOfDockers % 2 == 0) {
							var angle = this._getAngle(this.dockers[numOfDockers / 2 - 1], this.dockers[numOfDockers / 2])
							var pos1 = this.dockers[numOfDockers / 2 - 1].bounds.center();
							var pos2 = this.dockers[numOfDockers / 2].bounds.center();
							var pos = { x: (pos1.x + pos2.x) / 2.0, y: (pos1.y + pos2.y) / 2.0 };

							label.horizontalAlign("center");
							label.verticalAlign("bottom");
							label.x = pos.x;
							label.y = pos.y - label.getOffsetTop();

							if (angle <= 90 || angle > 270) {
								label.rotate(360 - angle, pos);
							} else {
								label.rotate(180 - angle, pos);
							}
						} else {
							var index = parseInt(numOfDockers / 2);
							var angle = this._getAngle(this.dockers[index], this.dockers[index + 1])
							var pos = this.dockers[index].bounds.center();

							if (angle <= 90 || angle > 270) {
								label.horizontalAlign("left");
								label.verticalAlign("bottom");
								label.x = pos.x + label.getOffsetTop();
								label.y = pos.y - label.getOffsetTop();
								label.rotate(360 - angle, pos);
							} else {
								label.horizontalAlign("right");
								label.verticalAlign("bottom");
								label.x = pos.x - label.getOffsetTop();
								label.y = pos.y - label.getOffsetTop();
								label.rotate(180 - angle, pos);
							}
						}

						break;
					case "midbottom":
						var numOfDockers = this.dockers.length;
						if (numOfDockers % 2 == 0) {
							var angle = this._getAngle(this.dockers[numOfDockers / 2 - 1], this.dockers[numOfDockers / 2])
							var pos1 = this.dockers[numOfDockers / 2 - 1].bounds.center();
							var pos2 = this.dockers[numOfDockers / 2].bounds.center();
							var pos = { x: (pos1.x + pos2.x) / 2.0, y: (pos1.y + pos2.y) / 2.0 };

							label.horizontalAlign("center");
							label.verticalAlign("top");
							label.x = pos.x;
							label.y = pos.y + label.getOffsetTop();

							if (angle <= 90 || angle > 270) {
								label.rotate(360 - angle, pos);
							} else {
								label.rotate(180 - angle, pos);
							}
						} else {
							var index = parseInt(numOfDockers / 2);
							var angle = this._getAngle(this.dockers[index], this.dockers[index + 1])
							var pos = this.dockers[index].bounds.center();

							if (angle <= 90 || angle > 270) {
								label.horizontalAlign("left");
								label.verticalAlign("top");
								label.x = pos.x + label.getOffsetBottom();
								label.y = pos.y + label.getOffsetBottom();
								label.rotate(360 - angle, pos);
							} else {
								label.horizontalAlign("right");
								label.verticalAlign("top");
								label.x = pos.x - label.getOffsetBottom();
								label.y = pos.y + label.getOffsetBottom();
								label.rotate(180 - angle, pos);
							}
						}

						break;
					case "endtop":
						var length = this.dockers.length;
						var angle = this._getAngle(this.dockers[length - 2], this.dockers[length - 1]);
						var pos = this.dockers.last().bounds.center();

						if (angle <= 90 || angle > 270) {
							label.horizontalAlign("right");
							label.verticalAlign("bottom");
							label.x = pos.x - label.getOffsetTop();
							label.y = pos.y - label.getOffsetTop();
							label.rotate(360 - angle, pos);
						} else {
							label.horizontalAlign("left");
							label.verticalAlign("bottom");
							label.x = pos.x + label.getOffsetTop();
							label.y = pos.y - label.getOffsetTop();
							label.rotate(180 - angle, pos);
						}

						break;
					case "endbottom":
						var length = this.dockers.length;
						var angle = this._getAngle(this.dockers[length - 2], this.dockers[length - 1]);
						var pos = this.dockers.last().bounds.center();

						if (angle <= 90 || angle > 270) {
							label.horizontalAlign("right");
							label.verticalAlign("top");
							label.x = pos.x - label.getOffsetBottom();
							label.y = pos.y + label.getOffsetBottom();
							label.rotate(360 - angle, pos);
						} else {
							label.horizontalAlign("left");
							label.verticalAlign("top");
							label.x = pos.x + label.getOffsetBottom();
							label.y = pos.y + label.getOffsetBottom();
							label.rotate(180 - angle, pos);
						}

						break;
				}
			}.bind(this));

			this.children.each(function (value) {
				if (value instanceof ORYX.Core.Node) {
					this.calculatePositionOfAttachedChildNode.call(this, value);
				}
			}.bind(this));

			this.refreshAttachedNodes();
			this.refresh();

			this.isChanged = false;
			this._dockerUpdated = false;

			this._oldBounds = this.bounds.clone();
		}


	},

	/**
	 *  Moves a point to the upperLeft of a node's bounds.
	 *  
	 *  @param {point} point
	 *  	The point to move
	 *  @param {ORYX.Core.Bounds} bounds
	 *  	The Bounds of the related noe
	 */
	movePointToUpperLeftOfNode: function (point, bounds) {
		point.x -= bounds.width() / 2;
		point.y -= bounds.height() / 2;
	},

	/**
	 * Refreshes the visual representation of edge's attached nodes.
	 */
	refreshAttachedNodes: function () {
		this.attachedNodePositionData.values().each(function (nodeData) {
			var startPoint = nodeData.segment.docker1.bounds.center();
			var endPoint = nodeData.segment.docker2.bounds.center();
			this.relativizePoint(startPoint);
			this.relativizePoint(endPoint);

			var newNodePosition = new Object();

			/* Calculate new x-coordinate */
			newNodePosition.x = startPoint.x
				+ nodeData.relativDistanceFromDocker1
				* (endPoint.x - startPoint.x);

			/* Calculate new y-coordinate */
			newNodePosition.y = startPoint.y
				+ nodeData.relativDistanceFromDocker1
				* (endPoint.y - startPoint.y);

			/* Convert new position to the upper left of the node */
			this.movePointToUpperLeftOfNode(newNodePosition, nodeData.node.bounds);

			/* Move node to its new position */
			nodeData.node.bounds.moveTo(newNodePosition);
			nodeData.node._update();

		}.bind(this));
	},

	/**
	 * Calculates the position of an edge's child node. The node is placed on 
	 * the path of the edge.
	 * 
	 * @param {node}
	 * 		The node to calculate the new position
	 * @return {Point}
	 * 		The calculated upper left point of the node's shape.
	 */
	calculatePositionOfAttachedChildNode: function (node) {
		/* Initialize position */
		var position = new Object();
		position.x = 0;
		position.y = 0;

		/* Case: Node was just added */
		if (!this.attachedNodePositionData[node.getId()]) {
			this.attachedNodePositionData[node.getId()] = new Object();
			this.attachedNodePositionData[node.getId()]
				.relativDistanceFromDocker1 = 0;
			this.attachedNodePositionData[node.getId()].node = node;
			this.attachedNodePositionData[node.getId()].segment = new Object();
			this.findEdgeSegmentForNode(node);
		} else if (node.isChanged) {
			this.findEdgeSegmentForNode(node);
		}



	},

	/**
	 * Finds the appropriate edge segement for a node.
	 * The segment is choosen, which has the smallest distance to the node.
	 * 
	 * @param {ORYX.Core.Node} node
	 * 		The concerning node
	 */
	findEdgeSegmentForNode: function (node) {
		var length = this.dockers.length;
		var smallestDistance = undefined;

		for (i = 1; i < length; i++) {
			var lineP1 = this.dockers[i - 1].bounds.center();
			var lineP2 = this.dockers[i].bounds.center();
			this.relativizePoint(lineP1);
			this.relativizePoint(lineP2);

			var nodeCenterPoint = node.bounds.center();
			var distance = ORYX.Core.Math.distancePointLinie(
				lineP1,
				lineP2,
				nodeCenterPoint,
				true);

			if ((distance || distance == 0) && ((!smallestDistance && smallestDistance != 0)
				|| distance < smallestDistance)) {

				smallestDistance = distance;

				this.attachedNodePositionData[node.getId()].segment.docker1 =
					this.dockers[i - 1];
				this.attachedNodePositionData[node.getId()].segment.docker2 =
					this.dockers[i];

			}

			/* Either the distance does not match the segment or the distance
			 * between docker1 and docker2 is 0
			 * 
			 * In this case choose the nearest docker as attaching point.
			 * 
			 */
			if (!distance && !smallestDistance && smallestDistance != 0) {
				(ORYX.Core.Math.getDistancePointToPoint(nodeCenterPoint, lineP1)
					< ORYX.Core.Math.getDistancePointToPoint(nodeCenterPoint, lineP2)) ?
					this.attachedNodePositionData[node.getId()].relativDistanceFromDocker1 = 0 :
					this.attachedNodePositionData[node.getId()].relativDistanceFromDocker1 = 1;
				this.attachedNodePositionData[node.getId()].segment.docker1 =
					this.dockers[i - 1];
				this.attachedNodePositionData[node.getId()].segment.docker2 =
					this.dockers[i];
			}
		}

		/* Calculate position on edge segment for the node */
		if (smallestDistance || smallestDistance == 0) {
			this.attachedNodePositionData[node.getId()].relativDistanceFromDocker1 =
				this.getLineParameterForPosition(
					this.attachedNodePositionData[node.getId()].segment.docker1,
					this.attachedNodePositionData[node.getId()].segment.docker2,
					node);
		}
	},


	/**
	 *
	 * @param {ORYX.Core.Node|Object} node or position
	 * @return {Object} An object with the following attribute: {ORYX.Core.Docker} fromDocker, {ORYX.Core.Docker} toDocker, {X/Y} position, {int} distance
	 */
	findSegment: function (node) {

		var length = this.dockers.length;
		var result;

		var nodeCenterPoint = node instanceof ORYX.Core.UIObject ? node.bounds.center() : node;

		for (i = 1; i < length; i++) {
			var lineP1 = this.dockers[i - 1].bounds.center();
			var lineP2 = this.dockers[i].bounds.center();

			var distance = ORYX.Core.Math.distancePointLinie(lineP1, lineP2, nodeCenterPoint, true);

			if (typeof distance == "number" && (result === undefined || distance < result.distance)) {
				result = {
					distance: distance,
					fromDocker: this.dockers[i - 1],
					toDocker: this.dockers[i]
				}

			}
		}
		return result;
	},

	/**
	 * Returns the value of the scalar to determine the position of the node on 
	 * line defined by docker1 and docker2.
	 * 
	 * @param {point} docker1
	 * 		The docker that defines the start of the line segment
	 * @param {point} docker2
	 * 		The docker that defines the end of the line segment
	 * @param {ORYX.Core.Node} node
	 * 		The concerning node
	 * 
	 * @return {float} positionParameter
	 * 		The scalar value to determine the position on the line
	 */
	getLineParameterForPosition: function (docker1, docker2, node) {
		var dockerPoint1 = docker1.bounds.center();
		var dockerPoint2 = docker2.bounds.center();
		this.relativizePoint(dockerPoint1);
		this.relativizePoint(dockerPoint2);

		var intersectionPoint = ORYX.Core.Math.getPointOfIntersectionPointLine(
			dockerPoint1,
			dockerPoint2,
			node.bounds.center(), true);
		if (!intersectionPoint) {
			return 0;
		}

		var relativeDistance =
			ORYX.Core.Math.getDistancePointToPoint(intersectionPoint, dockerPoint1) /
			ORYX.Core.Math.getDistancePointToPoint(dockerPoint1, dockerPoint2);

		return relativeDistance;
	},
	/**
	 * Makes point relative to the upper left of the edge's bound.
	 * 
	 * @param {point} point
	 * 		The point to relativize
	 */
	relativizePoint: function (point) {
		point.x -= this.bounds.upperLeft().x;
		point.y -= this.bounds.upperLeft().y;
	},

	/**
	 * Move the first and last docker and calls the refresh method.
	 * Attention: This does not calculates intersection point between the
	 * edge and the bounded nodes. This only works if only the nodes are
	 * moves.
	 *
	 */
	optimizedUpdate: function () {

		var updateDocker = function (docker) {
			if (!docker._dockedShape || !docker._dockedShapeBounds)
				return;
			var off = {
				x: docker._dockedShape.bounds.a.x - docker._dockedShapeBounds.a.x,
				y: docker._dockedShape.bounds.a.y - docker._dockedShapeBounds.a.y
			};
			docker.bounds.moveBy(off);
			docker._dockedShapeBounds.moveBy(off);
		}

		updateDocker(this.dockers.first());
		updateDocker(this.dockers.last());

		this.refresh();
	},

	refresh: function () {
		//call base class refresh method
		arguments.callee.$.refresh.apply(this, arguments);

		//TODO consider points for marker mids
		var lastPoint;
		this._paths.each((function (path, index) {
			var dockers = this._dockersByPath[path.id];
			var c = undefined;
			var d = undefined;
			if (lastPoint) {
				d = "M" + lastPoint.x + " " + lastPoint.y;
			}
			else {
				c = dockers[0].bounds.center();
				lastPoint = c;

				d = "M" + c.x + " " + c.y;
			}

			for (var i = 1; i < dockers.length; i++) {
				// for each docker, draw a line to the center
				c = dockers[i].bounds.center();
				d = d + "L" + c.x + " " + c.y + " ";
				lastPoint = c;
			}

			path.setAttributeNS(null, "d", d);
			this._interactionPaths[index].setAttributeNS(null, "d", d);

		}).bind(this));


		/* move child shapes of an edge */
		if (this.getChildNodes().length > 0) {
			var x = this.bounds.upperLeft().x;
			var y = this.bounds.upperLeft().y;

			this.node.firstChild.childNodes[1].setAttributeNS(null, "transform", "translate(" + x + ", " + y + ")");
		}

	},

    /**
     * Calculate the Border Intersection Point between two points
     * @param {PointA}
     * @param {PointB}
     */
	getIntersectionPoint: function () {

		var length = Math.floor(this.dockers.length / 2)

		return ORYX.Core.Math.midPoint(this.dockers[length - 1].bounds.center(), this.dockers[length].bounds.center())
	},

	/**
     * Returns TRUE if the bounds is over the edge
     * @param {Bounds}
     *
     */
	isBoundsIncluded: function (bounds) {
		var dockers = this.dockers, size = dockers.length;
		return dockers.any(function (docker, i) {
			if (i == size - 1) { return false; }
			var a = docker.bounds.center();
			var b = dockers[i + 1].bounds.center();

			return ORYX.Core.Math.isRectOverLine(a.x, a.y, b.x, b.y, bounds.a.x, bounds.a.y, bounds.b.x, bounds.b.y);
		});
	},

    /**
     * Calculate if the point is inside the Shape
     * @param {PointX}
     * @param {PointY} 
     */
	isPointIncluded: function (pointX, pointY) {

		var isbetweenAB = this.absoluteBounds().isIncluded(pointX, pointY,
			ORYX.CONFIG.OFFSET_EDGE_BOUNDS);

		var isPointIncluded = undefined;

		if (isbetweenAB && this.dockers.length > 0) {

			var i = 0;
			var point1, point2;


			do {

				point1 = this.dockers[i].bounds.center();
				point2 = this.dockers[++i].bounds.center();

				isPointIncluded = ORYX.Core.Math.isPointInLine(pointX, pointY,
					point1.x, point1.y,
					point2.x, point2.y,
					ORYX.CONFIG.OFFSET_EDGE_BOUNDS);

			} while (!isPointIncluded && i < this.dockers.length - 1)

		}

		return isPointIncluded;

	},


    /**
     * Calculate if the point is over an special offset area
     * @param {Point}
     */
	isPointOverOffset: function () {
		return false
	},

	/**
	 * Returns TRUE if the given node
	 * is a child node of the shapes node
	 * @param {Element} node
	 * @return {Boolean}
	 *
	 */
	containsNode: function (node) {
		if (this._paths.include(node) ||
			this._interactionPaths.include(node)) {
			return true;
		}
		return false;
	},

	/**
	* Returns the angle of the line between two dockers
	* (0 - 359.99999999)
	*/
	_getAngle: function (docker1, docker2) {
		var p1 = docker1 instanceof ORYX.Core.Controls.Docker ? docker1.absoluteCenterXY() : docker1;
		var p2 = docker2 instanceof ORYX.Core.Controls.Docker ? docker2.absoluteCenterXY() : docker2;

		return ORYX.Core.Math.getAngle(p1, p2);
	},

	alignDockers: function () {
		this._update(true);

		var firstPoint = this.dockers.first().bounds.center();
		var lastPoint = this.dockers.last().bounds.center();

		var deltaX = lastPoint.x - firstPoint.x;
		var deltaY = lastPoint.y - firstPoint.y;

		var numOfDockers = this.dockers.length - 1;

		this.dockers.each((function (docker, index) {
			var part = index / numOfDockers;
			docker.bounds.unregisterCallback(this._dockerChangedCallback);
			docker.bounds.moveTo(firstPoint.x + part * deltaX, firstPoint.y + part * deltaY);
			docker.bounds.registerCallback(this._dockerChangedCallback);
		}).bind(this));

		this._dockerChanged();
	},

	add: function (shape) {
		arguments.callee.$.add.apply(this, arguments);

		// If the new shape is a Docker which is not contained
		if (shape instanceof ORYX.Core.Controls.Docker && this.dockers.include(shape)) {
			// Add it to the dockers list ordered by paths		
			var pathArray = this._dockersByPath.values()[0];
			if (pathArray) {
				pathArray.splice(this.dockers.indexOf(shape), 0, shape);
			}

			/* Perform nessary adjustments on the edge's child shapes */
			this.handleChildShapesAfterAddDocker(shape);
		}
	},

	/**
	 * Performs nessary adjustments on the edge's child shapes.
	 * 
	 * @param {ORYX.Core.Controls.Docker} docker
	 * 		The added docker
	 */
	handleChildShapesAfterAddDocker: function (docker) {
		/* Ensure type of Docker */
		if (!docker instanceof ORYX.Core.Controls.Docker) { return undefined; }

		var index = this.dockers.indexOf(docker);
		if (!(0 < index && index < this.dockers.length - 1)) {
			/* Exception: Expect added docker between first and last node of the edge */
			return undefined;
		}

		/* Get child nodes concerning the segment of the new docker */
		var startDocker = this.dockers[index - 1];
		var endDocker = this.dockers[index + 1];

		/* Adjust the position of edge's child nodes */
		var segmentElements =
			this.getAttachedNodePositionDataForSegment(startDocker, endDocker);

		var lengthSegmentPart1 = ORYX.Core.Math.getDistancePointToPoint(
			startDocker.bounds.center(),
			docker.bounds.center());
		var lengthSegmentPart2 = ORYX.Core.Math.getDistancePointToPoint(
			endDocker.bounds.center(),
			docker.bounds.center());

		if (!(lengthSegmentPart1 + lengthSegmentPart2)) { return; }

		var relativDockerPosition = lengthSegmentPart1 / (lengthSegmentPart1 + lengthSegmentPart2);

		segmentElements.each(function (nodePositionData) {
			/* Assign child node to the new segment */
			if (nodePositionData.value.relativDistanceFromDocker1 < relativDockerPosition) {
				/* Case: before added Docker */
				nodePositionData.value.segment.docker2 = docker;
				nodePositionData.value.relativDistanceFromDocker1 =
					nodePositionData.value.relativDistanceFromDocker1 / relativDockerPosition;
			} else {
				/* Case: after added Docker */
				nodePositionData.value.segment.docker1 = docker;
				var newFullDistance = 1 - relativDockerPosition;
				var relativPartOfSegment =
					nodePositionData.value.relativDistanceFromDocker1
					- relativDockerPosition;

				nodePositionData.value.relativDistanceFromDocker1 =
					relativPartOfSegment / newFullDistance;

			}
		})


		// Update all labels reference points
		this.getLabels().each(function (label) {

			var ref = label.getReferencePoint();
			if (!ref) {
				return;
			}
			var index = this.dockers.indexOf(docker);
			if (index >= ref.segment.fromIndex && index <= ref.segment.toIndex) {

				var segment = this.findSegment(ref.intersection);
				if (!segment) {
					// Choose whether the first of the last segment
					segment.fromDocker = ref.segment.fromIndex >= (this.dockers.length / 2) ? this.dockers[0] : this.dockers[this.dockers.length - 2];
					segment.toDocker = this.dockers[this.dockers.indexOf(from) + 1]; // The next one if the to docker
				}

				var fromPosition = segment.fromDocker.bounds.center(), toPosition = segment.toDocker.bounds.center();

				var intersection = ORYX.Core.Math.getPointOfIntersectionPointLine(
					fromPosition, 		// P1 - Center of the first docker
					toPosition, 		// P2 - Center of the second docker
					ref.intersection, 	// P3 - Center of the label
					true);
				//var oldDistance = ORYX.Core.Math.getDistanceBetweenTwoPoints(ref.segment.fromPosition, ref.segment.toPosition, ref.intersection);
				//intersection = ORYX.Core.Math.getPointBetweenTwoPoints(fromPosition, toPosition, isNaN(oldDistance) ? 0.5 : (lengthOld*oldDistance)/lengthNew);

				// Update the reference point
				this.updateReferencePointOfLabel(label, intersection, segment.fromDocker, segment.toDocker, true);
			}
		}.bind(this));

		/* Update attached nodes visual representation */
		this.refreshAttachedNodes();
	},

	/**
	 *	Returns elements from {@link attachedNodePositiondata} that match the
	 *  segement defined by startDocker and endDocker.
	 *  
	 *  @param {ORYX.Core.Controls.Docker} startDocker
	 *  	The docker defining the begin of the segment.
	 *  @param {ORYX.Core.Controls.Docker} endDocker
	 *  	The docker defining the begin of the segment.
	 *  
	 *  @return {Hash} attachedNodePositionData
	 *  	Child elements matching the segment
	 */
	getAttachedNodePositionDataForSegment: function (startDocker, endDocker) {
		/* Ensure that the segment is defined correctly */
		if (!((startDocker instanceof ORYX.Core.Controls.Docker)
			&& (endDocker instanceof ORYX.Core.Controls.Docker))) {
			return [];
		}

		/* Get elements of the segment */
		var elementsOfSegment =
			this.attachedNodePositionData.findAll(function (nodePositionData) {
				return nodePositionData.value.segment.docker1 === startDocker &&
					nodePositionData.value.segment.docker2 === endDocker;
			});

		/* Return a Hash in each case */
		if (!elementsOfSegment) { return []; }

		return elementsOfSegment;
	},

	/**
	 * Removes an edge's child shape
	 */
	remove: function (shape) {
		arguments.callee.$.remove.apply(this, arguments);

		if (this.attachedNodePositionData[shape.getId()]) {
			delete this.attachedNodePositionData[shape.getId()];
		}

		/* Adjust child shapes if neccessary */
		if (shape instanceof ORYX.Core.Controls.Docker) {
			this.handleChildShapesAfterRemoveDocker(shape);
		}
	},

	updateReferencePointOfLabel: function (label, intersection, from, to, dirty) {
		if (!label.getReferencePoint() || !label.isVisible) {
			return;
		}

		var ref = label.getReferencePoint();

		//
		if (ref.orientation && ref.orientation !== "ce") {
			var angle = this._getAngle(from, to);
			if (ref.distance >= 0) {
				if (angle == 0) {
					label.horizontalAlign("left");//ref.orientation == "lr" ? "right" : "left");
					label.verticalAlign("bottom");
				} else if (angle > 0 && angle < 90) {
					label.horizontalAlign("right");
					label.verticalAlign("bottom");
				} else if (angle == 90) {
					label.horizontalAlign("right");
					label.verticalAlign("top");//ref.orientation == "lr" ? "bottom" : "top");
				} else if (angle > 90 && angle < 180) {
					label.horizontalAlign("right");
					label.verticalAlign("top");
				} else if (angle == 180) {
					label.horizontalAlign("left");//ref.orientation == "ur" ? "right" : "left");
					label.verticalAlign("top");
				} else if (angle > 180 && angle < 270) {
					label.horizontalAlign("left");
					label.verticalAlign("top");
				} else if (angle == 270) {
					label.horizontalAlign("left");
					label.verticalAlign("top");//ref.orientation == "ll" ? "bottom" : "top");
				} else if (angle > 270 && angle <= 360) {
					label.horizontalAlign("left");
					label.verticalAlign("bottom");
				}
			} else {
				if (angle == 0) {
					label.horizontalAlign("left");//ref.orientation == "ur" ? "right" : "left");
					label.verticalAlign("top");
				} else if (angle > 0 && angle < 90) {
					label.horizontalAlign("left");
					label.verticalAlign("top");
				} else if (angle == 90) {
					label.horizontalAlign("left");
					label.verticalAlign("top");//ref.orientation == "ll" ? "bottom" : "top");
				} else if (angle > 90 && angle < 180) {
					label.horizontalAlign("left");
					label.verticalAlign("bottom");
				} else if (angle == 180) {
					label.horizontalAlign("left");//ref.orientation == "lr" ? "right" : "left");
					label.verticalAlign("bottom");
				} else if (angle > 180 && angle < 270) {
					label.horizontalAlign("right");
					label.verticalAlign("bottom");
				} else if (angle == 270) {
					label.horizontalAlign("right");
					label.verticalAlign("top");//ref.orientation == "lr" ? "bottom" : "top");
				} else if (angle > 270 && angle <= 360) {
					label.horizontalAlign("right");
					label.verticalAlign("top");
				}
			}
			ref.iorientation = ref.iorientation || ref.orientation;
			ref.orientation = (label.verticalAlign() == "top" ? "u" : "l") + (label.horizontalAlign() == "left" ? "l" : "r");
		}

		label.setReferencePoint(Ext.apply({}, {
			intersection: intersection,
			segment: {
				from: from,
				fromIndex: this.dockers.indexOf(from),
				fromPosition: from.bounds.center(),
				to: to,
				toIndex: this.dockers.indexOf(to),
				toPosition: to.bounds.center()
			},
			dirty: dirty || false
		}, ref))
	},
	/**
	 * 	Adjusts the child shapes of an edges after a docker was removed.
	 * 	
	 *  @param{ORYX.Core.Controls.Docker} docker
	 *  	The removed docker.
	 */
	handleChildShapesAfterRemoveDocker: function (docker) {
		/* Ensure docker type */
		if (!(docker instanceof ORYX.Core.Controls.Docker)) { return; }

		this.attachedNodePositionData.each(function (nodePositionData) {
			if (nodePositionData.value.segment.docker1 === docker) {
				/* The new start of the segment is the predecessor of docker2. */
				var index = this.dockers.indexOf(nodePositionData.value.segment.docker2);
				if (index == -1) { return; }
				nodePositionData.value.segment.docker1 = this.dockers[index - 1];
			}
			else if (nodePositionData.value.segment.docker2 === docker) {
				/* The new end of the segment is the successor of docker1. */
				var index = this.dockers.indexOf(nodePositionData.value.segment.docker1);
				if (index == -1) { return; }
				nodePositionData.value.segment.docker2 = this.dockers[index + 1];
			}
		}.bind(this));

		// Update all labels reference points
		this.getLabels().each(function (label) {

			var ref = label.getReferencePoint();
			if (!ref) {
				return;
			}
			var from = ref.segment.from;
			var to = ref.segment.to;

			if (from !== docker && to !== docker) {
				return;
			}

			var segment = this.findSegment(ref.intersection);
			if (!segment) {
				from = segment.fromDocker;
				to = segment.toDocker;
			} else {
				from = from === docker ? this.dockers[this.dockers.indexOf(to) - 1] : from;
				to = this.dockers[this.dockers.indexOf(from) + 1];
			}

			var intersection = ORYX.Core.Math.getPointOfIntersectionPointLine(from.bounds.center(), to.bounds.center(), ref.intersection, true);
			// Update the reference point
			this.updateReferencePointOfLabel(label, intersection, from, to, true);
		}.bind(this));

		/* Update attached nodes visual representation */
		this.refreshAttachedNodes();
	},

	/**
     *@deprecated Use the .createDocker() Method and set the point via the bounds
     */
	addDocker: function (position, exDocker) {
		var lastDocker;
		var result;
		this._dockersByPath.any((function (pair) {
			return pair.value.any((function (docker, index) {
				if (!lastDocker) {
					lastDocker = docker;
					return false;
				}
				else {
					var point1 = lastDocker.bounds.center();
					var point2 = docker.bounds.center();

					if (ORYX.Core.Math.isPointInLine(position.x, position.y, point1.x, point1.y, point2.x, point2.y, 10)) {
						var path = this._paths.find(function (path) {
							return path.id === pair.key;
						});
						if (path) {
							var allowAttr = path.getAttributeNS(NAMESPACE_ORYX, 'allowDockers');
							if (allowAttr && allowAttr.toLowerCase() === "no") {
								return true;
							}
						}
						var newDocker = (exDocker) ? exDocker : this.createDocker(this.dockers.indexOf(lastDocker) + 1, position);
						newDocker.bounds.centerMoveTo(position);
						if (exDocker)
							this.add(newDocker, this.dockers.indexOf(lastDocker) + 1);
						// Remove new Docker from 'to add' dockers
						//pair.value = pair.value.without(newDocker);
						//pair.value.splice(this.dockers.indexOf(lastDocker) + 1, 0, newDocker);
						// Remove the Docker from the Docker list and add the Docker to the new position
						//this.dockers = this.dockers.without(newDocker);
						//this.dockers.splice(this.dockers.indexOf(lastDocker) + 1, 0, newDocker);
						//this._update(true);
						result = newDocker;
						return true;
					}
					else {
						lastDocker = docker;
						return false;
					}
				}
			}).bind(this));
		}).bind(this));
		return result;
	},

	removeDocker: function (docker) {
		if (this.dockers.length > 2 && !(this.dockers.first() === docker)) {
			this._dockersByPath.any((function (pair) {
				if (pair.value.member(docker)) {
					if (docker === pair.value.last()) {
						return true;
					}
					else {
						this.remove(docker);
						this._dockersByPath[pair.key] = pair.value.without(docker);
						this.isChanged = true;
						this._dockerChanged();
						return true;
					}
				}
				return false;
			}).bind(this));
		}
	},

	/**
	 * Removes all dockers from the edge which are on 
	 * the line between two dockers
	 * @return {Object} Removed dockers in an indicied array 
	 * (key is the removed position of the docker, value is docker themselve)
	 */
	removeUnusedDockers: function () {
		var marked = $H({});

		this.dockers.each(function (docker, i) {
			if (i == 0 || i == this.dockers.length - 1) { return }
			var previous = this.dockers[i - 1];

			/* Do not consider already removed dockers */
			if (marked.values().indexOf(previous) != -1 && this.dockers[i - 2]) {
				previous = this.dockers[i - 2];
			}
			var next = this.dockers[i + 1];

			var cp = previous.getDockedShape() && previous.referencePoint ? previous.getAbsoluteReferencePoint() : previous.bounds.center();
			var cn = next.getDockedShape() && next.referencePoint ? next.getAbsoluteReferencePoint() : next.bounds.center();
			var cd = docker.bounds.center();

			if (ORYX.Core.Math.isPointInLine(cd.x, cd.y, cp.x, cp.y, cn.x, cn.y, 1)) {
				marked[i] = docker;
			}
		}.bind(this))

		marked.each(function (docker) {
			this.removeDocker(docker.value);
		}.bind(this))

		if (marked.values().length > 0) {
			this._update(true);
		}

		return marked;
	},

    /**
     * Initializes the Edge after loading the SVG representation of the edge.
     * @param {SVGDocument} svgDocument
     */
	_init: function (svgDocument) {
		arguments.callee.$._init.apply(this, arguments);

		var minPointX, minPointY, maxPointX, maxPointY;

		//init markers
		var defs = svgDocument.getElementsByTagNameNS(NAMESPACE_SVG, "defs");
		if (defs.length > 0) {
			defs = defs[0];
			var markerElements = $A(defs.getElementsByTagNameNS(NAMESPACE_SVG, "marker"));
			var marker;
			var me = this;
			markerElements.each(function (markerElement) {
				try {
					marker = new ORYX.Core.SVG.SVGMarker(markerElement.cloneNode(true));
					me._markers[marker.id] = marker;
					var textElements = $A(marker.element.getElementsByTagNameNS(NAMESPACE_SVG, "text"));
					var label;
					textElements.each(function (textElement) {
						label = new ORYX.Core.SVG.Label({
							textElement: textElement,
							shapeId: this.id
						});
						me._labels[label.id] = label;
					});
				}
				catch (e) {
				}
			});
		}


		var gs = svgDocument.getElementsByTagNameNS(NAMESPACE_SVG, "g");
		if (gs.length <= 0) {
			throw "Edge: No g element found.";
		}
		var g = gs[0];


		g.setAttributeNS(null, "id", null);

		var isFirst = true;

		$A(g.childNodes).each((function (path, index) {
			if (ORYX.Editor.checkClassType(path, SVGPathElement)) {
				path = path.cloneNode(false);

				var pathId = this.id + "_" + index;
				path.setAttributeNS(null, "id", pathId);
				this._paths.push(path);

				//check, if markers are set and update the id
				var markersByThisPath = [];
				var markerUrl = path.getAttributeNS(null, "marker-start");

				if (markerUrl && markerUrl !== "") {
					markerUrl = markerUrl.strip();
					markerUrl = markerUrl.replace(/^url\(#/, '');
					var markerStartId = this.id.concat(markerUrl.replace(/\)$/, ''));
					path.setAttributeNS(null, "marker-start", "url(#" + markerStartId + ")");

					markersByThisPath.push(this._markers[markerStartId]);
				}

				markerUrl = path.getAttributeNS(null, "marker-mid");

				if (markerUrl && markerUrl !== "") {
					markerUrl = markerUrl.strip();
					markerUrl = markerUrl.replace(/^url\(#/, '');
					var markerMidId = this.id.concat(markerUrl.replace(/\)$/, ''));
					path.setAttributeNS(null, "marker-mid", "url(#" + markerMidId + ")");

					markersByThisPath.push(this._markers[markerMidId]);
				}

				markerUrl = path.getAttributeNS(null, "marker-end");

				if (markerUrl && markerUrl !== "") {
					markerUrl = markerUrl.strip();
					markerUrl = markerUrl.replace(/^url\(#/, '');
					var markerEndId = this.id.concat(markerUrl.replace(/\)$/, ''));
					path.setAttributeNS(null, "marker-end", "url(#" + markerEndId + ")");

					markersByThisPath.push(this._markers[markerEndId]);
				}

				this._markersByPath[pathId] = markersByThisPath;

				//init dockers
				var parser = new PathParser();
				var handler = new ORYX.Core.SVG.PointsPathHandler();
				parser.setHandler(handler);
				parser.parsePath(path);

				if (handler.points.length < 4) {
					throw "Edge: Path has to have two or more points specified.";
				}

				this._dockersByPath[pathId] = [];

				for (var i = 0; i < handler.points.length; i += 2) {
					//handler.points.each((function(point, pIndex){
					var x = handler.points[i];
					var y = handler.points[i + 1];
					if (isFirst || i > 0) {
						var docker = new ORYX.Core.Controls.Docker({
							eventHandlerCallback: this.eventHandlerCallback
						});
						docker.bounds.centerMoveTo(x, y);
						docker.bounds.registerCallback(this._dockerChangedCallback);
						this.add(docker, this.dockers.length);

						//this._dockersByPath[pathId].push(docker);

						//calculate minPoint and maxPoint
						if (minPointX) {
							minPointX = Math.min(x, minPointX);
							minPointY = Math.min(y, minPointY);
						}
						else {
							minPointX = x;
							minPointY = y;
						}

						if (maxPointX) {
							maxPointX = Math.max(x, maxPointX);
							maxPointY = Math.max(y, maxPointY);
						}
						else {
							maxPointX = x;
							maxPointY = y;
						}
					}
					//}).bind(this));
				}
				isFirst = false;
			}
		}).bind(this));

		this.bounds.set(minPointX, minPointY, maxPointX, maxPointY);

		if (false && (this.bounds.width() === 0 || this.bounds.height() === 0)) {
			var width = this.bounds.width();
			var height = this.bounds.height();

			this.bounds.extend({
				x: width === 0 ? 2 : 0,
				y: height === 0 ? 2 : 0
			});

			this.bounds.moveBy({
				x: width === 0 ? -1 : 0,
				y: height === 0 ? -1 : 0
			});

		}

		this._oldBounds = this.bounds.clone();

		//add paths to this.node
		this._paths.reverse();
		var paths = [];
		this._paths.each((function (path) {
			paths.push(this.node.childNodes[0].childNodes[0].childNodes[0].appendChild(path));
		}).bind(this));

		this._paths = paths;

		//init interaction path
		this._paths.each((function (path) {
			var iPath = path.cloneNode(false);
			iPath.setAttributeNS(null, "id", undefined);
			iPath.setAttributeNS(null, "stroke-width", 10);
			iPath.setAttributeNS(null, "visibility", "hidden");
			iPath.setAttributeNS(null, "stroke-dasharray", null);
			iPath.setAttributeNS(null, "stroke", "black");
			iPath.setAttributeNS(null, "fill", "none");
			iPath.setAttributeNS(null, "title", this.getStencil().title());
			this._interactionPaths.push(this.node.childNodes[0].childNodes[0].childNodes[0].appendChild(iPath));
		}).bind(this));

		this._paths.reverse();
		this._interactionPaths.reverse();

		/**initialize labels*/
		var textElems = svgDocument.getElementsByTagNameNS(ORYX.CONFIG.NAMESPACE_SVG, 'text');

		$A(textElems).each((function (textElem) {
			var label = new ORYX.Core.SVG.Label({
				textElement: textElem,
				shapeId: this.id
			});
			this.node.childNodes[0].childNodes[0].appendChild(label.node);
			this._labels[label.id] = label;

			label.registerOnChange(this.layout.bind(this));
		}).bind(this));


		this.propertiesChanged.each(function (pair) {
			pair.value = true;
		});

		//this._update(true);
	},

    /**
     * Adds all necessary markers of this Edge to the SVG document.
     * Has to be called, while this.node is part of DOM.
     */
	addMarkers: function (defs) {
		this._markers.each(function (marker) {
			if (!defs.ownerDocument.getElementById(marker.value.id)) {
				marker.value.element = defs.appendChild(marker.value.element);
			}
		});
	},

    /**
     * Removes all necessary markers of this Edge from the SVG document.
     * Has to be called, while this.node is part of DOM.
     */
	removeMarkers: function () {
		var svgElement = this.node.ownerSVGElement;
		if (svgElement) {
			var defs = svgElement.getElementsByTagNameNS(NAMESPACE_SVG, "defs");
			if (defs.length > 0) {
				defs = defs[0];
				this._markers.each(function (marker) {
					var foundMarker = defs.ownerDocument.getElementById(marker.value.id);
					if (foundMarker) {
						marker.value.element = defs.removeChild(marker.value.element);
					}
				});
			}
		}
	},

    /**
     * Calls when a docker has changed
     */
	_dockerChanged: function () {

		//this._update(true);
		this._dockerUpdated = true;

	},

	serialize: function () {
		var result = arguments.callee.$.serialize.apply(this);

		//add dockers triple
		var value = "";
		this._dockersByPath.each((function (pair) {
			pair.value.each(function (docker) {
				var position = docker.getDockedShape() && docker.referencePoint ? docker.referencePoint : docker.bounds.center();
				value = value.concat(position.x + " " + position.y + " ");
			});

			value += " # ";
		}).bind(this));
		result.push({
			name: 'dockers',
			prefix: 'oryx',
			value: value,
			type: 'literal'
		});

		//add parent triple dependant on the dockedShapes
		//TODO change this when canvas becomes a resource


		//serialize target and source
		var lastDocker = this.dockers.last();

		var target = lastDocker.getDockedShape();

		if (target) {
			result.push({
				name: 'target',
				prefix: 'raziel',
				value: '#' + ERDF.__stripHashes(target.resourceId),
				type: 'resource'
			});
		}

		try {
			//result = this.getStencil().serialize(this, result);
			var serializeEvent = this.getStencil().serialize();

			/*
			 * call serialize callback by reference, result should be found
			 * in serializeEvent.result
			 */
			if (serializeEvent.type) {
				serializeEvent.shape = this;
				serializeEvent.data = result;
				serializeEvent.result = undefined;
				serializeEvent.forceExecution = true;

				this._delegateEvent(serializeEvent);

				if (serializeEvent.result) {
					result = serializeEvent.result;
				}
			}
		}
		catch (e) {
		}
		return result;
	},

	deserialize: function (data) {
		try {
			//data = this.getStencil().deserialize(this, data);

			var deserializeEvent = this.getStencil().deserialize();

			/*
			 * call serialize callback by reference, result should be found
			 * in serializeEventInfo.result
			 */
			if (deserializeEvent.type) {
				deserializeEvent.shape = this;
				deserializeEvent.data = data;
				deserializeEvent.result = undefined;
				deserializeEvent.forceExecution = true;

				this._delegateEvent(deserializeEvent);
				if (deserializeEvent.result) {
					data = deserializeEvent.result;
				}
			}
		}
		catch (e) {
		}

		// Set the outgoing shapes
		var target = data.find(function (ser) { return (ser.prefix + "-" + ser.name) == 'raziel-target' });
		var targetShape;
		if (target) {
			targetShape = this.getCanvas().getChildShapeByResourceId(target.value);
		}

		var outgoing = data.findAll(function (ser) { return (ser.prefix + "-" + ser.name) == 'raziel-outgoing' });
		outgoing.each((function (obj) {
			// TODO: Look at Canvas
			if (!this.parent) { return };

			// Set outgoing Shape
			var next = this.getCanvas().getChildShapeByResourceId(obj.value);

			if (next) {
				if (next == targetShape) {
					// If this is an edge, set the last docker to the next shape
					this.dockers.last().setDockedShape(next);
					this.dockers.last().setReferencePoint({ x: next.bounds.width() / 2.0, y: next.bounds.height() / 2.0 });
				} else if (next instanceof ORYX.Core.Edge) {
					//Set the first docker of the next shape
					next.dockers.first().setDockedShape(this);
					//next.dockers.first().setReferencePoint({x: this.bounds.width() / 2.0, y: this.bounds.height() / 2.0});
				} /*else if(next.dockers.length > 0) { //next is a node and next has a docker
					next.dockers.first().setDockedShape(this);
					next.dockers.first().setReferencePoint({x: this.bounds.width() / 2.0, y: this.bounds.height() / 2.0});
				}*/
			}

		}).bind(this));


		var oryxDockers = data.find(function (obj) {
			return (obj.prefix === "oryx" &&
				obj.name === "dockers");
		});

		if (oryxDockers) {
			var dataByPath = oryxDockers.value.split("#").without("").without(" ");

			dataByPath.each((function (data, index) {
				var values = data.replace(/,/g, " ").split(" ").without("");

				//for each docker two values must be defined
				if (values.length % 2 === 0) {
					var path = this._paths[index];

					if (path) {
						if (index === 0) {
							while (this._dockersByPath[path.id].length > 2) {
								this.removeDocker(this._dockersByPath[path.id][1]);
							}
						}
						else {
							while (this._dockersByPath[path.id].length > 1) {
								this.removeDocker(this._dockersByPath[path.id][0]);
							}
						}

						var dockersByPath = this._dockersByPath[path.id];

						if (index === 0) {
							//set position of first docker
							var x = parseFloat(values.shift());
							var y = parseFloat(values.shift());

							if (dockersByPath.first().getDockedShape()) {
								dockersByPath.first().setReferencePoint({
									x: x,
									y: y
								});
							}
							else {
								dockersByPath.first().bounds.centerMoveTo(x, y);
							}
						}

						//set position of last docker
						y = parseFloat(values.pop());
						x = parseFloat(values.pop());

						if (dockersByPath.last().getDockedShape()) {
							dockersByPath.last().setReferencePoint({
								x: x,
								y: y
							});
						} else {
							dockersByPath.last().bounds.centerMoveTo(x, y);
						}

						//add additional dockers
						for (var i = 0; i < values.length; i++) {
							x = parseFloat(values[i]);
							y = parseFloat(values[++i]);

							var newDocker = this.createDocker();
							newDocker.bounds.centerMoveTo(x, y);

							//this.dockers = this.dockers.without(newDocker);
							//this.dockers.splice(this.dockers.indexOf(dockersByPath.last()), 0, newDocker);
							//dockersByPath.splice(this.dockers.indexOf(dockersByPath.last()), 0, newDocker);
						}
					}
				}
			}).bind(this));
		} else {
			this.alignDockers();
		}

		arguments.callee.$.deserialize.apply(this, arguments);

		this._changed();
	},

	toString: function () {
		return this.getStencil().title() + " " + this.id;
	},

    /**
     * @return {ORYX.Core.Shape} Returns last docked shape or null.
     */
	getTarget: function () {
		return this.dockers.last() ? this.dockers.last().getDockedShape() : null;
	},

	/**
	 * @return {ORYX.Core.Shape} Returns the first docked shape or null
	 */
	getSource: function () {
		return this.dockers.first() ? this.dockers.first().getDockedShape() : null;
	},

	/**
	 * Checks whether the edge is at least docked to one shape.
	 * 
	 * @return {boolean} True if edge is docked
	 */
	isDocked: function () {
		var isDocked = false;
		this.dockers.each(function (docker) {
			if (docker.isDocked()) {
				isDocked = true;
				throw $break;
			}
		});
		return isDocked;
	},

    /**
     * Calls {@link ORYX.Core.AbstractShape#toJSON} and add a some stencil set information.
     */
	toJSON: function () {
		var json = arguments.callee.$.toJSON.apply(this, arguments);

		if (this.getTarget()) {
			json.target = {
				resourceId: this.getTarget().resourceId
			};
		}

		return json;
	}
};
ORYX.Core.Edge = ORYX.Core.Shape.extend(ORYX.Core.Edge);
/**
 * Copyright (c) 2009
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX) { var ORYX = {} }
if (!ORYX.Plugins) { ORYX.Plugins = {} }

/**
   This abstract plugin class can be used to build plugins on.
   It provides some more basic functionality like registering events (on*-handlers)...
   @example
    ORYX.Plugins.MyPlugin = ORYX.Plugins.AbstractPlugin.extend({
        construct: function() {
            // Call super class constructor
            arguments.callee.$.construct.apply(this, arguments);
            
            [...]
        },
        [...]
    });
   
   @class ORYX.Plugins.AbstractPlugin
   @constructor Creates a new instance
   @author Willi Tscheschner
*/
ORYX.Plugins.AbstractPlugin = Clazz.extend({
    /** 
     * The facade which offer editor-specific functionality
     * @type Facade
     * @memberOf ORYX.Plugins.AbstractPlugin.prototype
     */
	facade: null,

	construct: function (facade) {
		this.facade = facade;

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_LOADED, this.onLoaded.bind(this));
	},

    /**
       Overwrite to handle load event. TODO: Document params!!!
       @methodOf ORYX.Plugins.AbstractPlugin.prototype
    */
	onLoaded: function () { },

    /**
       Overwrite to handle selection changed event. TODO: Document params!!!
       @methodOf ORYX.Plugins.AbstractPlugin.prototype
    */
	onSelectionChanged: function () { },

    /**
       Show overlay on given shape.
       @methodOf ORYX.Plugins.AbstractPlugin.prototype
       @example
       showOverlay(
           myShape,
           { stroke: "green" },
           ORYX.Editor.graft("http://www.w3.org/2000/svg", null, ['path', {
               "title": "Click the element to execute it!",
               "stroke-width": 2.0,
               "stroke": "black",
               "d": "M0,-5 L5,0 L0,5 Z",
               "line-captions": "round"
           }])
       )
       @param {Oryx.XXX.Shape[]} shapes One shape or array of shapes the overlay should be put on
       @param {Oryx.XXX.Attributes} attributes some attributes...
       @param {Oryx.svg.node} svgNode The svg node which should be used as overlay
       @param {String} [svgNode="NW"] The svg node position where the overlay should be placed
    */
	showOverlay: function (shapes, attributes, svgNode, svgNodePosition) {

		if (!(shapes instanceof Array)) {
			shapes = [shapes]
		}

		// Define Shapes
		shapes = shapes.map(function (shape) {
			var el = shape;
			if (typeof shape == "string") {
				el = this.facade.getCanvas().getChildShapeByResourceId(shape);
				el = el || this.facade.getCanvas().getChildById(shape, true);
			}
			return el;
		}.bind(this)).compact();

		// Define unified id
		if (!this.overlayID) {
			this.overlayID = this.type + ORYX.Editor.provideId();
		}

		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_OVERLAY_SHOW,
			id: this.overlayID,
			shapes: shapes,
			attributes: attributes,
			node: svgNode,
			nodePosition: svgNodePosition || "NW"
		});

	},

    /**
       Hide current overlay.
       @methodOf ORYX.Plugins.AbstractPlugin.prototype
    */
	hideOverlay: function () {
		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_OVERLAY_HIDE,
			id: this.overlayID
		});
	},

    /**
       Does a transformation with the given xslt stylesheet.
       @methodOf ORYX.Plugins.AbstractPlugin.prototype
       @param {String} data The data (e.g. eRDF) which should be transformed
       @param {String} stylesheet URL of a stylesheet which should be used for transforming data.
    */
	doTransform: function (data, stylesheet) {

		if (!stylesheet || !data) {
			return ""
		}

		var parser = new DOMParser();
		var parsedData = parser.parseFromString(data, "text/xml");
		source = stylesheet;
		new Ajax.Request(source, {
			asynchronous: false,
			method: 'get',
			onSuccess: function (transport) {
				xsl = transport.responseText
			}.bind(this),
			onFailure: (function (transport) {
				ORYX.Log.error("XSL load failed" + transport);
			}).bind(this)
		});
		var xsltProcessor = new XSLTProcessor();
		var domParser = new DOMParser();
		var xslObject = domParser.parseFromString(xsl, "text/xml");
		xsltProcessor.importStylesheet(xslObject);

		try {

			var newData = xsltProcessor.transformToFragment(parsedData, document);
			var serializedData = (new XMLSerializer()).serializeToString(newData);

			/* Firefox 2 to 3 problem?! */
			serializedData = !serializedData.startsWith("<?xml") ? "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + serializedData : serializedData;

			return serializedData;

		} catch (error) {
			return -1;
		}

	},

	/**
	 * Opens a new window that shows the given XML content.
	 * @methodOf ORYX.Plugins.AbstractPlugin.prototype
	 * @param {Object} content The XML content to be shown.
	 * @example
	 * openDownloadWindow( "my.xml", "<exampleXML />" );
	 */
	openXMLWindow: function (content) {
		var win = window.open(
			'data:application/xml,' + encodeURIComponent(
				content
			),
			'_blank', "resizable=yes,width=600,height=600,toolbar=0,scrollbars=yes"
		);
	},

    /**
     * Opens a download window for downloading the given content.
     * @methodOf ORYX.Plugins.AbstractPlugin.prototype
     * @param {String} filename The content's file name
     * @param {String} content The content to download
     */
	openDownloadWindow: function (filename, content) {
		var win = window.open("");
		if (win != null) {
			win.document.open();
			win.document.write("<html><body>");
			var submitForm = win.document.createElement("form");
			win.document.body.appendChild(submitForm);

			var createHiddenElement = function (name, value) {
				var newElement = document.createElement("input");
				newElement.name = name;
				newElement.type = "hidden";
				newElement.value = value;
				return newElement
			}

			submitForm.appendChild(createHiddenElement("download", content));
			submitForm.appendChild(createHiddenElement("file", filename));


			submitForm.method = "POST";
			win.document.write("</body></html>");
			win.document.close();
			submitForm.action = ORYX.PATH + "//download";
			submitForm.submit();
		}
	},

    /**
     * Serializes DOM.
     * @methodOf ORYX.Plugins.AbstractPlugin.prototype
     * @type {String} Serialized DOM
     */
	getSerializedDOM: function () {
		// Force to set all resource IDs
		var serializedDOM = DataManager.serializeDOM(this.facade);

		//add namespaces
		serializedDOM = '<?xml version="1.0" encoding="utf-8"?>' +
			'<html xmlns="http://www.w3.org/1999/xhtml" ' +
			'xmlns:b3mn="http://b3mn.org/2007/b3mn" ' +
			'xmlns:ext="http://b3mn.org/2007/ext" ' +
			'xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ' +
			'xmlns:atom="http://b3mn.org/2007/atom+xhtml">' +
			'<head profile="http://purl.org/NET/erdf/profile">' +
			'<link rel="schema.dc" href="http://purl.org/dc/elements/1.1/" />' +
			'<link rel="schema.dcTerms" href="http://purl.org/dc/terms/ " />' +
			'<link rel="schema.b3mn" href="http://b3mn.org" />' +
			'<link rel="schema.oryx" href="http://oryx-editor.org/" />' +
			'<link rel="schema.raziel" href="http://raziel.org/" />' +
			'<base href="' +
			location.href.split("?")[0] +
			'" />' +
			'</head><body>' +
			serializedDOM +
			'</body></html>';

		return serializedDOM;
	},

    /**
     * Sets the editor in read only mode: Edges/ dockers cannot be moved anymore,
     * shapes cannot be selected anymore.
     * @methodOf ORYX.Plugins.AbstractPlugin.prototype
     */
	enableReadOnlyMode: function () {
		//Edges cannot be moved anymore
		this.facade.disableEvent(ORYX.CONFIG.EVENT_MOUSEDOWN);

		// Stop the user from editing the diagram while the plugin is active
		this._stopSelectionChange = function () {
			if (this.facade.getSelection().length > 0) {
				this.facade.setSelection([]);
			}
		};
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, this._stopSelectionChange.bind(this));
	},
    /**
     * Disables read only mode, see @see
     * @methodOf ORYX.Plugins.AbstractPlugin.prototype
     * @see ORYX.Plugins.AbstractPlugin.prototype.enableReadOnlyMode
     */
	disableReadOnlyMode: function () {
		// Edges can be moved now again
		this.facade.enableEvent(ORYX.CONFIG.EVENT_MOUSEDOWN);

		if (this._stopSelectionChange) {
			this.facade.unregisterOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, this._stopSelectionChange.bind(this));
			this._stopSelectionChange = undefined;
		}
	},

    /**
     * Extracts RDF from DOM.
     * @methodOf ORYX.Plugins.AbstractPlugin.prototype
     * @type {String} Extracted RFD. Null if there are transformation errors.
     */
	getRDFFromDOM: function () {
		//convert to RDF
		try {
			var xsl = "";
			source = ORYX.PATH + "/lib/extract-rdf.xsl";
			new Ajax.Request(source, {
				asynchronous: false,
				method: 'get',
				onSuccess: function (transport) {
					xsl = transport.responseText
				}.bind(this),
				onFailure: (function (transport) {
					ORYX.Log.error("XSL load failed" + transport);
				}).bind(this)
			});
			/*
			 var parser = new DOMParser();
			 var parsedDOM = parser.parseFromString(this.getSerializedDOM(), "text/xml");
			 var xsltPath = ORYX.PATH + "/lib/extract-rdf.xsl";
			 var xsltProcessor = new XSLTProcessor();
			 var xslRef = document.implementation.createDocument("", "", null);
			 xslRef.async = false;
			 xslRef.load(xsltPath);
			 xsltProcessor.importStylesheet(xslRef);
			 try {
			 var rdf = xsltProcessor.transformToDocument(parsedDOM);
			 return (new XMLSerializer()).serializeToString(rdf);
			 } catch (error) {
			 Ext.Msg.alert("Oryx", error);
			 return null;
			 }*/
			var domParser = new DOMParser();
			var xmlObject = domParser.parseFromString(this.getSerializedDOM(), "text/xml");
			var xslObject = domParser.parseFromString(xsl, "text/xml");
			var xsltProcessor = new XSLTProcessor();
			xsltProcessor.importStylesheet(xslObject);
			var result = xsltProcessor.transformToFragment(xmlObject, document);

			var serializer = new XMLSerializer();

			return serializer.serializeToString(result);
		} catch (e) {
			Ext.Msg.alert("Oryx", error);
			return "";
		}


	},

    /**
	 * Checks if a certain stencil set is loaded right now.
	 * 
	 */
	isStencilSetExtensionLoaded: function (stencilSetExtensionNamespace) {
		return this.facade.getStencilSets().values().any(
			function (ss) {
				return ss.extensions().keys().any(
					function (extensionKey) {
						return extensionKey == stencilSetExtensionNamespace;
					}.bind(this)
				);
			}.bind(this)
		);
	},

	/**
	 * Raises an event so that registered layouters does
	 * have the posiblility to layout the given shapes 
	 * For further reading, have a look into the AbstractLayouter
	 * class
	 * @param {Object} shapes
	 */
	doLayout: function (shapes) {
		// Raises a do layout event
		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_LAYOUT,
			shapes: shapes
		});
	},
	/**
	 * Does a primitive layouting with the incoming/outgoing 
	 * edges (set the dockers to the right position) and if 
	 * necessary, it will be called the real layouting 
	 * @param {ORYX.Core.Node} node
	 * @param {Array} edges
	 */
	layoutEdges: function (node, allEdges, offset) {

		if (!this.facade.isExecutingCommands()) { return }

		var Command = ORYX.Core.Command.extend({
			construct: function (edges, node, offset, plugin) {
				this.edges = edges;
				this.node = node;
				this.plugin = plugin;
				this.offset = offset;

				// Get the new absolute center
				var center = node.absoluteXY();
				this.ulo = { x: center.x - offset.x, y: center.y - offset.y };


			},
			execute: function () {

				if (this.changes) {
					this.executeAgain();
					return;
				} else {
					this.changes = [];
					this.edges.each(function (edge) {
						this.changes.push({
							edge: edge,
							oldDockerPositions: edge.dockers.map(function (r) { return r.bounds.center() })
						})
					}.bind(this));
				}

				// Find all edges, which are related to the node and
				// have more than two dockers
				this.edges
					// Find all edges with more than two dockers
					.findAll(function (r) { return r.dockers.length > 2 }.bind(this))
					// For every edge, check second and one before last docker
					// if there are horizontal/vertical on the same level
					// and if so, align the the bounds 
					.each(function (edge) {
						if (edge.dockers.first().getDockedShape() === this.node) {
							var second = edge.dockers[1];
							if (this.align(second.bounds, edge.dockers.first())) { second.update(); }
						} else if (edge.dockers.last().getDockedShape() === this.node) {
							var beforeLast = edge.dockers[edge.dockers.length - 2];
							if (this.align(beforeLast.bounds, edge.dockers.last())) { beforeLast.update(); }
						}
						edge._update(true);
						edge.removeUnusedDockers();
						if (this.isBendPointIncluded(edge)) {
							this.plugin.doLayout(edge);
							return;
						}
					}.bind(this));


				// Find all edges, which have only to dockers 
				// and is located horizontal/vertical.
				// Do layout with those edges
				this.edges
					// Find all edges with exactly two dockers
					.each(function (edge) {
						if (edge.dockers.length == 2) {
							var p1 = edge.dockers.first().getAbsoluteReferencePoint() || edge.dockers.first().bounds.center();
							var p2 = edge.dockers.last().getAbsoluteReferencePoint() || edge.dockers.first().bounds.center();
							// Find all horizontal/vertical edges
							if (Math.abs(-Math.abs(p1.x - p2.x) + Math.abs(this.offset.x)) < 2 || Math.abs(-Math.abs(p1.y - p2.y) + Math.abs(this.offset.y)) < 2) {
								this.plugin.doLayout(edge);
							}
						}
					}.bind(this));

				this.edges.each(function (edge, i) {
					this.changes[i].dockerPositions = edge.dockers.map(function (r) { return r.bounds.center() });
				}.bind(this));

			},
			/**
			 * Align the bounds if the center is 
			 * the same than the old center
			 * @params {Object} bounds
			 * @params {Object} bounds2
			 */
			align: function (bounds, refDocker) {

				var abRef = refDocker.getAbsoluteReferencePoint() || refDocker.bounds.center();

				var xdif = bounds.center().x - abRef.x;
				var ydif = bounds.center().y - abRef.y;
				if (Math.abs(-Math.abs(xdif) + Math.abs(this.offset.x)) < 3 && this.offset.xs === undefined) {
					bounds.moveBy({ x: -xdif, y: 0 })
				}
				if (Math.abs(-Math.abs(ydif) + Math.abs(this.offset.y)) < 3 && this.offset.ys === undefined) {
					bounds.moveBy({ y: -ydif, x: 0 })
				}

				if (this.offset.xs !== undefined || this.offset.ys !== undefined) {
					var absPXY = refDocker.getDockedShape().absoluteXY();
					xdif = bounds.center().x - (absPXY.x + ((abRef.x - absPXY.x) / this.offset.xs));
					ydif = bounds.center().y - (absPXY.y + ((abRef.y - absPXY.y) / this.offset.ys));

					if (Math.abs(-Math.abs(xdif) + Math.abs(this.offset.x)) < 3) {
						bounds.moveBy({ x: -(bounds.center().x - abRef.x), y: 0 })
					}

					if (Math.abs(-Math.abs(ydif) + Math.abs(this.offset.y)) < 3) {
						bounds.moveBy({ y: -(bounds.center().y - abRef.y), x: 0 })
					}
				}
			},

			/**						
			 * Returns a TRUE if there are bend point which overlay the shape
			 */
			isBendPointIncluded: function (edge) {
				// Get absolute bounds
				var ab = edge.dockers.first().getDockedShape();
				var bb = edge.dockers.last().getDockedShape();

				if (ab) {
					ab = ab.absoluteBounds();
					ab.widen(5);
				}

				if (bb) {
					bb = bb.absoluteBounds();
					bb.widen(20); // Wide with 20 because of the arrow from the edge
				}

				return edge.dockers
					.any(function (docker, i) {
						var c = docker.bounds.center();
						// Dont count first and last
						return i != 0 && i != edge.dockers.length - 1 &&
							// Check if the point is included to the absolute bounds
							((ab && ab.isIncluded(c)) || (bb && bb.isIncluded(c)))
					})
			},

			removeAllDocker: function (edge) {
				edge.dockers.slice(1, edge.dockers.length - 1).each(function (docker) {
					edge.removeDocker(docker);
				})
			},
			executeAgain: function () {
				this.changes.each(function (change) {
					// Reset the dockers
					this.removeAllDocker(change.edge);
					change.dockerPositions.each(function (pos, i) {
						if (i == 0 || i == change.dockerPositions.length - 1) { return }
						var docker = change.edge.createDocker(undefined, pos);
						docker.bounds.centerMoveTo(pos);
						docker.update();
					}.bind(this));
					change.edge._update(true);
				}.bind(this));
			},
			rollback: function () {
				this.changes.each(function (change) {
					// Reset the dockers
					this.removeAllDocker(change.edge);
					change.oldDockerPositions.each(function (pos, i) {
						if (i == 0 || i == change.oldDockerPositions.length - 1) { return }
						var docker = change.edge.createDocker(undefined, pos);
						docker.bounds.centerMoveTo(pos);
						docker.update();
					}.bind(this));
					change.edge._update(true);
				}.bind(this));
			}
		});

		this.facade.executeCommands([new Command(allEdges, node, offset, this)]);

	}
});
/**
 * Copyright (c) 2009
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX) { var ORYX = {} }
if (!ORYX.Plugins) { ORYX.Plugins = {} }

/**
   This abstract plugin implements the core behaviour of layout
   
   @class ORYX.Plugins.AbstractLayouter
   @constructor Creates a new instance
   @author Willi Tscheschner
*/
ORYX.Plugins.AbstractLayouter = ORYX.Plugins.AbstractPlugin.extend({

	/**
	 * 'layouted' defined all types of shapes which will be layouted. 
	 * It can be one value or an array of values. The value
	 * can be a Stencil ID (as String) or an class type of either 
	 * a ORYX.Core.Node or ORYX.Core.Edge
     * @type Array|String|Object
     * @memberOf ORYX.Plugins.AbstractLayouter.prototype
	 */
	layouted: [],

	/**
	 * Constructor
	 * @param {Object} facade
	 * @memberOf ORYX.Plugins.AbstractLayouter.prototype
	 */
	construct: function (facade) {
		arguments.callee.$.construct.apply(this, arguments);

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_LAYOUT, this._initLayout.bind(this));
	},

	/**
	 * Proofs if this shape should be layouted or not
	 * @param {Object} shape
     * @memberOf ORYX.Plugins.AbstractLayouter.prototype
	 */
	isIncludedInLayout: function (shape) {
		if (!(this.layouted instanceof Array)) {
			this.layouted = [this.layouted].compact();
		}

		// If there are no elements
		if (this.layouted.length <= 0) {
			// Return TRUE
			return true;
		}

		// Return TRUE if there is any correlation between 
		// the 'layouted' attribute and the shape themselve.
		return this.layouted.any(function (s) {
			if (typeof s == "string") {
				return shape.getStencil().id().include(s);
			} else {
				return shape instanceof s;
			}
		})
	},

	/**
	 * Callback to start the layouting
	 * @param {Object} event Layout event
	 * @param {Object} shapes Given shapes
     * @memberOf ORYX.Plugins.AbstractLayouter.prototype
	 */
	_initLayout: function (event) {

		// Get the shapes
		var shapes = [event.shapes].flatten().compact();

		// Find all shapes which should be layouted
		var toLayout = shapes.findAll(function (shape) {
			return this.isIncludedInLayout(shape)
		}.bind(this))

		// If there are shapes left 
		if (toLayout.length > 0) {
			// Do layout
			this.layout(toLayout);
		}
	},

	/**
	 * Implementation of layouting a set on shapes
	 * @param {Object} shapes Given shapes
     * @memberOf ORYX.Plugins.AbstractLayouter.prototype
	 */
	layout: function (shapes) {
		throw new Error("Layouter has to implement the layout function.")
	}
}); ImageViewer = Ext.extend(Ext.Window, {
	initComponent: function () {
		this.bodyCfg = {
			tag: 'img',
			src: this.src,
			autoscroll: true,
			fixedcenter: true
		};
		ImageViewer.superclass.initComponent.apply(this, arguments);
	},

	onRender: function () {
		ImageViewer.superclass.onRender.apply(this, arguments);
		this.body.on('load', this.onImageLoad, this, { single: true });
	},

	onImageLoad: function () {
		// var h = this.getFrameHeight(),
		// w = this.getFrameWidth();
		// this.setSize(this.body.dom.offsetWidth + w, this.body.dom.offsetHeight + h);
	},

	setSrc: function (src) {
		this.body.on('load', this.onImageLoad, this, { single: true });
		//this.body.dom.style.width = this.body.dom.style.width = 'auto';
		this.body.dom.src = src;
	},

	initEvents: function () {
		ImageViewer.superclass.initEvents.apply(this, arguments);
		if (this.resizer) {
			this.resizer.preserveRatio = true;
		}
	}
}); if (!Signavio) { var Signavio = {} };
if (!Signavio.Core) { Signavio.Core = {} };
Signavio.Core.Version = "1.0";
/**
* Copyright (c) 2009
*
* Willi Tscheschner
* 
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
**/

if (!Signavio) {
	var Signavio = new Object();
}

if (!Signavio.Plugins) {
	Signavio.Plugins = new Object();
}

if (!Signavio.Plugins.Utils) {
	Signavio.Plugins.Utils = new Object();
}

if (!Signavio.Helper) {
	Signavio.Helper = new Object();
}


new function () {

	var mask;


	Signavio.Plugins.Utils.getFFVersion = function () {
		try {
			return Number(window.navigator.userAgent.match("Firefox.([0-9]+[\.][0-9]+)")[1]) || 0;
		} catch (e) {
			return 0;
		}
	}


	/**
	 * Shows an overlay of signavio
	 */
	Signavio.Helper.ShowMask = function (force, parent) {

		if (!force && ORYX.CONFIG.PREVENT_LOADINGMASK_AT_READY === true) {
			return;
		}

		if (mask) {
			return;
		}

		var s = "background:white;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100000;"
		var ss = "left:50%;margin-left:-200px;margin-top:-90px;position:absolute;top:50%;display:none;width:391px;"
		var sversion = "color:#ad0f5b;padding-right:10px;font-family:tahoma,arial,san-serif;font-size:12px;";
		var stext = "display:block;position:relative;text-align:right;top:0;width:100%;";
		var stitle = "color:#ad0f5b;font-weight:bold;padding-right:10px;font-family:tahoma,arial,san-serif;font-size:12px;"
		var sloading = "height:16px;width:16px;margin-bottom:-4px;background: transparent url(" + ORYX.CONFIG.SERVER_HANDLER_ROOT + "/libs/ext-2.0.2/resources/images/default/tree/loading.gif) no-repeat center;"
		var simg = "padding-bottom:10px;border-bottom:1px solid #ad0f5b;";

		// Define the parent
		parent = (parent ? Ext.get(parent) : null) || Ext.getBody();

		if (parent !== Ext.getBody()) {
			parent.setStyle("position", "relative")
		}

		mask = Ext.get(document.createElement("div"));
		parent.appendChild(mask);
		mask.dom.setAttribute("style", s);
		mask.dom.innerHTML = "<div class='mask-logo' style='" + ss + "'>" +
			"<div>" +
			"<img style='" + simg + "' src='" + ORYX.CONFIG.EXPLORER_PATH + "/src/img/signavio/smoky/logo.png' />" +
			"</div>" +
			"<span class='mask-text' style='" + stext + "'>" +
			"<span class='mask-title' style='" + stitle + "'>快搭科技(上海)流程设计器</span>" +
			"<span class='mask-version' style='" + sversion + "'>版本 " + Signavio.Core.Version + "</span>" +
			"<img style='" + sloading + "' src='" + (ORYX.CONFIG.BLANK_IMAGE || Ext.BLANK_IMAGE_URL) + "'/>" +
			"</span>" +
			"</div>";

		mask.first().show({ duration: 0.3 });

	}

	// When body is loaded, show overlay		
	Ext.onReady(Signavio.Helper.ShowMask);

	/**
	 * Hides the overlay
	 */
	Signavio.Helper.HideMask = function () {
		window.setTimeout(function () {
			if (mask) {
				mask.first().hide({ duration: 0.4, remove: true, block: true });
				mask.hide({ duration: 0.3, remove: true, block: true });
				delete mask;
			}

		}.bind(this), 2000)
	}

	Signavio.Plugins.Loading = {

		facade: undefined,
		construct: function (facade) {
			this.facade = facade;

			this.facade.registerOnEvent(ORYX.CONFIG.EVENT_LOADED, Signavio.Helper.HideMask);
		}
	}

	Signavio.Plugins.Loading = Clazz.extend(Signavio.Plugins.Loading);

	/**
	 * Provides an uniq id
	 * @overwrite
	 * @return {String}
	 *
	 */
	ORYX.Editor.provideId = function () {
		var res = [], hex = '0123456789ABCDEF';

		for (var i = 0; i < 36; i++) res[i] = Math.floor(Math.random() * 0x10);

		res[14] = 4;
		res[19] = (res[19] & 0x3) | 0x8;

		for (var i = 0; i < 36; i++) res[i] = hex[res[i]];

		res[8] = res[13] = res[18] = res[23] = '-';

		return "sid-" + res.join('');
	};


}();


/**
 * Ext specific extension
 * 
 * 
 * 
 */
new function () {

	/**
	 * Implementation of an Ext-LinkButton
	 * 
	 * 
	 */
	Ext.LinkButton = Ext.extend(Ext.BoxComponent, {

		// On Click Handler
		click: null,

		// Image url 
		image: null,

		// Image style (only if an image url is setted) 
		imageStyle: null,

		toggle: false,

		toggleStyle: null,

		selected: false,

		href: false,

		el: null,

		// private
		onRender: function (ct, position) {

			if (this.el == null) {

				this.el = document.createElement('a');

				if (this.tabIndex)
					this.el.setAttribute("tabindex", this.tabIndex)

				this.el.id = this.getId();
				this.el.className = this.cls || "x-link-button";

				if (!this.disabled)
					this.el.href = this.href ? this.href : "#" + this.text;

				if (!this.disabled) {
					Element.observe(this.el, 'click', this.onClick.bind(this));
				}

				if (this.image) {
					this.el.innerHTML = '<img src="' + this.image + '" title="' + this.text + '"' + (this.imageStyle ? ' style="' + this.imageStyle + '"/>' : '/>')
				} else {
					this.el.innerHTML = this.text ? Ext.util.Format.htmlEncode(this.text) : (this.html || '');
				}

				if (this.forId) {
					this.el.setAttribute('htmlFor', this.forId);
				}

			}

			Ext.LinkButton.superclass.onRender.call(this, ct, position);

		},

		onClick: function (e) {

			if (this.disabled) { Event.stop(e); return; }

			// Toggle the button
			if (this.toggle) {
				this.selected = !this.selected;
				if (this.toggleStyle) {
					this._setStyle(this.el.dom, '')
					this.el.dom.setAttribute('style', '')
					if (this.selected) {
						this.el.applyStyles(this.toggleStyle)
					} else {
						this.el.applyStyles(this.initialConfig.style)
					}
				}
			}


			if (this.click instanceof Function)
				this.click.apply(this.click, [this, e]);

			Event.stop(e)
		},

		setText: function (t, encode) {
			this.text = t;
			if (this.rendered) {
				this.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(t) : t;
			}
			return this;
		},

		_setStyle: function (node, style) {
			if (Ext.isIE) {
				node.style.setAttribute('cssText', style);
			} else {
				node.setAttribute('style', style);
			}
		}
	});

	Ext.reg('linkbutton', Ext.LinkButton);

}();


/**
 * Helper Methods
 * 
 */

new function () {


	Signavio.Helper.RecordReader = function (meta) {
		meta = meta || {};
		this.rels = meta.rels || this.rels;
		Signavio.Helper.RecordReader.superclass.constructor.call(this, meta, ['rep', 'href', 'rel']);
	};
	Ext.extend(Signavio.Helper.RecordReader, Ext.data.JsonReader, {

		rels: ["gitem"],

		read: function (response) {
			var json = response.responseText;
			var o = eval("(" + json + ")");
			if (!o) {
				throw { message: "JsonReader.read: Json object not found" };
			}
			var Record = this.recordType;
			var records = [], total = 0;
			o.each(function (rec) {
				if (this.rels.include(rec.rel)) {
					records.push(new Record(rec));
				}
				if (rec.rel == "info" && rec.rep.size) {
					total = rec.rep.size;
				}
			}.bind(this))
			return {
				success: true,
				records: records,
				totalRecords: total || records.length
			}
		}
	})



	/**
	 * Creates a new record, including 'rel', 'href', and 'rep' attributes
	 * @param {String} rel
	 * @param {String} href
	 * @param {Object} rep
	 */
	Signavio.Helper.createRecord = function (rel, href, rep) {

		var Rec = Ext.data.Record.create(["rel", "href", "rep"]);

		var record = new Rec({
			rel: rel,
			href: href,
			rep: rep
		});

		return record;
	}
}()

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/


if (!ORYX.Plugins) {
	ORYX.Plugins = new Object();
}

ORYX.Plugins.Toolbar = Clazz.extend({

	facade: undefined,
	plugs: [],

	construct: function (facade, ownPluginData) {
		this.facade = facade;

		this.groupIndex = new Hash();
		ownPluginData.properties.each((function (value) {
			if (value.group && value.index != undefined) {
				this.groupIndex[value.group] = value.index
			}
		}).bind(this));

		Ext.QuickTips.init();

		this.buttons = [];
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_BUTTON_UPDATE, this.onButtonUpdate.bind(this));
	},

    /**
     * Can be used to manipulate the state of a button.
     * @example
     * this.facade.raiseEvent({
     *   type: ORYX.CONFIG.EVENT_BUTTON_UPDATE,
     *   id: this.buttonId, // have to be generated before and set in the offer method
     *   pressed: true
     * });
     * @param {Object} event
     */
	onButtonUpdate: function (event) {
		var button = this.buttons.find(function (button) {
			return button.id === event.id;
		});

		if (event.pressed !== undefined) {
			button.buttonInstance.toggle(event.pressed);
		}
	},

	registryChanged: function (pluginsData) {
		// Sort plugins by group and index
		var newPlugs = pluginsData.sortBy((function (value) {
			return ((this.groupIndex[value.group] != undefined ? this.groupIndex[value.group] : "") + value.group + "" + value.index).toLowerCase();
		}).bind(this));
		var plugs = $A(newPlugs).findAll(function (value) {
			return !this.plugs.include(value) && (!value.target || value.target === ORYX.Plugins.Toolbar)
		}.bind(this));
		if (plugs.length < 1)
			return;

		this.buttons = [];

		ORYX.Log.trace("Creating a toolbar.")
		if (!this.toolbar) {
			this.toolbar = new Ext.ux.SlicedToolbar({
				height: 24
			});
			var region = this.facade.addToRegion("north", this.toolbar, "Toolbar");
		}


		var currentGroupsName = this.plugs.last() ? this.plugs.last().group : plugs[0].group;

		// Map used to store all drop down buttons of current group
		var currentGroupsDropDownButton = {};


		plugs.each((function (value) {
			if (!value.name) { return }
			this.plugs.push(value);
			// Add seperator if new group begins
			if (currentGroupsName != value.group) {
				this.toolbar.add('-');
				currentGroupsName = value.group;
				currentGroupsDropDownButton = {};
			}

			// If an drop down group icon is provided, a split button should be used
			if (value.dropDownGroupIcon) {
				var splitButton = currentGroupsDropDownButton[value.dropDownGroupIcon];

				// Create a new split button if this is the first plugin using it 
				if (splitButton === undefined) {
					splitButton = currentGroupsDropDownButton[value.dropDownGroupIcon] = new Ext.Toolbar.SplitButton({
						cls: "x-btn-icon", //show icon only
						icon: value.dropDownGroupIcon,
						menu: new Ext.menu.Menu({
							items: [] // items are added later on
						}),
						listeners: {
							click: function (button, event) {
								// The "normal" button should behave like the arrow button
								if (!button.menu.isVisible() && !button.ignoreNextClick) {
									button.showMenu();
								} else {
									button.hideMenu();
								}
							}
						}
					});

					this.toolbar.add(splitButton);
				}

				// General config button which will be used either to create a normal button
				// or a check button (if toggling is enabled)
				var buttonCfg = {
					icon: value.icon,
					text: value.name,
					itemId: value.id,
					handler: value.toggle ? undefined : value.functionality,
					checkHandler: value.toggle ? value.functionality : undefined,
					listeners: {
						render: function (item) {
							// After rendering, a tool tip should be added to component
							if (value.description) {
								new Ext.ToolTip({
									target: item.getEl(),
									title: value.description
								})
							}
						}
					}
				}

				// Create buttons depending on toggle
				if (value.toggle) {
					var button = new Ext.menu.CheckItem(buttonCfg);
				} else {
					var button = new Ext.menu.Item(buttonCfg);
				}

				splitButton.menu.add(button);

			} else if (value.addFill) {
				this.toolbar.addFill();
			} else { // create normal, simple button
				var button = new Ext.Toolbar.Button({
					icon: value.icon,         // icons can also be specified inline
					cls: 'x-btn-icon',       // Class who shows only the icon
					itemId: value.id,
					tooltip: value.description,  // Set the tooltip
					tooltipType: 'title',            // Tooltip will be shown as in the html-title attribute
					handler: value.toggle ? null : value.functionality,  // Handler for mouse click
					enableToggle: value.toggle, // Option for enabling toggling
					toggleHandler: value.toggle ? value.functionality : null // Handler for toggle (Parameters: button, active)
				});

				this.toolbar.add(button);

				button.getEl().onclick = function () { this.blur() }
			}

			value['buttonInstance'] = button;
			this.buttons.push(value);

		}).bind(this));

		this.enableButtons([]);

		//TODO this should be done when resizing and adding elements!!!!
		this.toolbar.calcSlices();
		window.addEventListener("resize", function (event) { this.toolbar.calcSlices() }.bind(this), false);
		window.addEventListener("onresize", function (event) { this.toolbar.calcSlices() }.bind(this), false);

	},

	onSelectionChanged: function (event) {
		this.enableButtons(event.elements);
	},

	enableButtons: function (elements) {
		// Show the Buttons
		this.buttons.each((function (value) {
			value.buttonInstance.enable();

			// If there is less elements than minShapes
			if (value.minShape && value.minShape > elements.length)
				value.buttonInstance.disable();
			// If there is more elements than minShapes
			if (value.maxShape && value.maxShape < elements.length)
				value.buttonInstance.disable();
			// If the plugin is not enabled	
			if (value.isEnabled && !value.isEnabled(value.buttonInstance))
				value.buttonInstance.disable();

		}).bind(this));
	}
});

Ext.ns("Ext.ux");
Ext.ux.SlicedToolbar = Ext.extend(Ext.Toolbar, {
	currentSlice: 0,
	iconStandardWidth: 22, //22 px 
	seperatorStandardWidth: 2, //2px, minwidth for Ext.Toolbar.Fill
	toolbarStandardPadding: 2,

	initComponent: function () {
		Ext.apply(this, {
		});
		Ext.ux.SlicedToolbar.superclass.initComponent.apply(this, arguments);
	},

	onRender: function () {
		Ext.ux.SlicedToolbar.superclass.onRender.apply(this, arguments);
	},

	onResize: function () {
		Ext.ux.SlicedToolbar.superclass.onResize.apply(this, arguments);
	},

	calcSlices: function () {
		var slice = 0;
		this.sliceMap = {};
		var sliceWidth = 0;
		var toolbarWidth = this.getEl().getWidth();

		this.items.getRange().each(function (item, index) {
			//Remove all next and prev buttons
			if (item.helperItem) {
				item.destroy();
				return;
			}

			var itemWidth = item.getEl().getWidth();

			if (sliceWidth + itemWidth + 5 * this.iconStandardWidth > toolbarWidth) {
				var itemIndex = this.items.indexOf(item);

				this.insertSlicingButton("next", slice, itemIndex);

				if (slice !== 0) {
					this.insertSlicingButton("prev", slice, itemIndex);
				}

				this.insertSlicingSeperator(slice, itemIndex);

				slice += 1;
				sliceWidth = 0;
			}

			this.sliceMap[item.id] = slice;
			sliceWidth += itemWidth;
		}.bind(this));

		// Add prev button at the end
		if (slice > 0) {
			this.insertSlicingSeperator(slice, this.items.getCount() + 1);
			this.insertSlicingButton("prev", slice, this.items.getCount() + 1);
			var spacer = new Ext.Toolbar.Spacer();
			this.insertSlicedHelperButton(spacer, slice, this.items.getCount() + 1);
			Ext.get(spacer.id).setWidth(this.iconStandardWidth);
		}

		this.maxSlice = slice;

		// Update view
		this.setCurrentSlice(this.currentSlice);
	},

	insertSlicedButton: function (button, slice, index) {
		this.insertButton(index, button);
		this.sliceMap[button.id] = slice;
	},

	insertSlicedHelperButton: function (button, slice, index) {
		button.helperItem = true;
		this.insertSlicedButton(button, slice, index);
	},

	insertSlicingSeperator: function (slice, index) {
		// Align right
		this.insertSlicedHelperButton(new Ext.Toolbar.Fill(), slice, index);
	},

	// type => next or prev
	insertSlicingButton: function (type, slice, index) {
		var nextHandler = function () { this.setCurrentSlice(this.currentSlice + 1) }.bind(this);
		var prevHandler = function () { this.setCurrentSlice(this.currentSlice - 1) }.bind(this);

		var button = new Ext.Toolbar.Button({
			cls: "x-btn-icon",
			icon: ORYX.CONFIG.ROOT_PATH + "/images/toolbar_" + type + ".png",
			handler: (type === "next") ? nextHandler : prevHandler
		});

		this.insertSlicedHelperButton(button, slice, index);
	},

	setCurrentSlice: function (slice) {
		if (slice > this.maxSlice || slice < 0) return;

		this.currentSlice = slice;

		this.items.getRange().each(function (item) {
			item.setVisible(slice === this.sliceMap[item.id]);
		}.bind(this));
	}
});/**
 * Copyright (c) 2009
 * Jan-Felix Schwarz, Willi Tscheschner, Nicolas Peters, Martin Czuchra, Daniel Polak
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins) {
	ORYX.Plugins = new Object();
}

ORYX.Plugins.ShapeMenuPlugin = {

	construct: function (facade) {
		this.facade = facade;

		this.alignGroups = new Hash();

		var containerNode = this.facade.getCanvas().getHTMLContainer();

		this.shapeMenu = new ORYX.Plugins.ShapeMenu(containerNode);
		this.currentShapes = [];

		// Register on dragging and resizing events for show/hide of ShapeMenu
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDROP_START, this.hideShapeMenu.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDROP_END, this.showShapeMenu.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_RESIZE_START, (function () {
			this.hideShapeMenu();
			this.hideMorphMenu();
		}).bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_RESIZE_END, this.showShapeMenu.bind(this));

		// Enable DragZone
		var DragZone = new Ext.dd.DragZone(containerNode.parentNode, { shadow: !Ext.isMac });
		DragZone.afterDragDrop = this.afterDragging.bind(this, DragZone);
		DragZone.beforeDragOver = this.beforeDragOver.bind(this, DragZone);

		// Memory of created Buttons
		this.createdButtons = {};

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_STENCIL_SET_LOADED, (function () { this.registryChanged() }).bind(this));

		this.timer = null;

		this.resetElements = true;

	},

	hideShapeMenu: function (event) {
		window.clearTimeout(this.timer);
		this.timer = null;
		this.shapeMenu.hide();
	},

	showShapeMenu: function (dontGenerateNew) {

		if (!dontGenerateNew || this.resetElements) {

			window.clearTimeout(this.timer);
			this.timer = window.setTimeout(function () {

				// Close all Buttons
				this.shapeMenu.closeAllButtons();

				// Show the Morph Button
				this.showMorphButton(this.currentShapes);

				// Show the Stencil Buttons
				this.showStencilButtons(this.currentShapes);

				// Show the ShapeMenu
				this.shapeMenu.show(this.currentShapes);

				this.resetElements = false;
			}.bind(this), 300)

		} else {

			window.clearTimeout(this.timer);
			this.timer = null;

			// Show the ShapeMenu
			this.shapeMenu.show(this.currentShapes);

		}
	},

	registryChanged: function (pluginsData) {

		if (pluginsData) {
			pluginsData = pluginsData.each(function (value) { value.group = value.group ? value.group : 'unknown' });
			this.pluginsData = pluginsData.sortBy(function (value) {
				return (value.group + "" + value.index);
			});
		}

		this.shapeMenu.removeAllButtons();
		this.shapeMenu.setNumberOfButtonsPerLevel(ORYX.CONFIG.SHAPEMENU_RIGHT, 2);
		this.createdButtons = {};

		this.createMorphMenu();

		if (!this.pluginsData) {
			this.pluginsData = [];
		}

		this.baseMorphStencils = this.facade.getRules().baseMorphs();

		// Checks if the stencil set has morphing attributes
		var isMorphing = this.facade.getRules().containsMorphingRules();

		// Create Buttons for all Stencils of all loaded stencilsets
		var stencilsets = this.facade.getStencilSets();
		stencilsets.values().each((function (stencilSet) {

			var nodes = stencilSet.nodes();
			nodes.each((function (stencil) {

				// Create a button for each node
				var option = { type: stencil.id(), namespace: stencil.namespace(), connectingType: true };
				var button = new ORYX.Plugins.ShapeMenuButton({
					callback: this.newShape.bind(this, option),
					icon: stencil.icon(),
					align: ORYX.CONFIG.SHAPEMENU_RIGHT,
					group: 0,
					//dragcallback: this.hideShapeMenu.bind(this),
					msg: stencil.title() + " - " + ORYX.I18N.ShapeMenuPlugin.clickDrag
				});

				// Add button to shape menu
				this.shapeMenu.addButton(button);

				// Add to the created Button Array
				this.createdButtons[stencil.namespace() + stencil.type() + stencil.id()] = button;

				// Drag'n'Drop will enable
				Ext.dd.Registry.register(button.node.lastChild, option);
			}).bind(this));


			var edges = stencilSet.edges();
			edges.each((function (stencil) {

				// Create a button for each edge
				var option = { type: stencil.id(), namespace: stencil.namespace() };
				var button = new ORYX.Plugins.ShapeMenuButton({
					callback: this.newShape.bind(this, option),
					// icon: 		isMorphing ? ORYX.PATH + "/images/edges.png" : stencil.icon(),
					icon: stencil.icon(),
					align: ORYX.CONFIG.SHAPEMENU_RIGHT,
					group: 1,
					//dragcallback: this.hideShapeMenu.bind(this),
					msg: (isMorphing ? ORYX.I18N.Edge : stencil.title()) + " - " + ORYX.I18N.ShapeMenuPlugin.drag
				});

				// Add button to shape menu
				this.shapeMenu.addButton(button);

				// Add to the created Button Array
				this.createdButtons[stencil.namespace() + stencil.type() + stencil.id()] = button;

				// Drag'n'Drop will enable
				Ext.dd.Registry.register(button.node.lastChild, option);

			}).bind(this));

		}).bind(this));

	},

	createMorphMenu: function () {

		this.morphMenu = new Ext.menu.Menu({
			id: 'Oryx_morph_menu',
			items: []
		});

		this.morphMenu.on("mouseover", function () {
			this.morphMenuHovered = true;
		}, this);
		this.morphMenu.on("mouseout", function () {
			this.morphMenuHovered = false;
		}, this);


		// Create the button to show the morph menu
		var button = new ORYX.Plugins.ShapeMenuButton({
			hovercallback: (ORYX.CONFIG.ENABLE_MORPHMENU_BY_HOVER ? this.showMorphMenu.bind(this) : undefined),
			resetcallback: (ORYX.CONFIG.ENABLE_MORPHMENU_BY_HOVER ? this.hideMorphMenu.bind(this) : undefined),
			callback: (ORYX.CONFIG.ENABLE_MORPHMENU_BY_HOVER ? undefined : this.toggleMorphMenu.bind(this)),
			icon: ORYX.PATH + '/images/wrench_orange.png',
			align: ORYX.CONFIG.SHAPEMENU_BOTTOM,
			group: 0,
			msg: ORYX.I18N.ShapeMenuPlugin.morphMsg
		});

		this.shapeMenu.setNumberOfButtonsPerLevel(ORYX.CONFIG.SHAPEMENU_BOTTOM, 1)
		this.shapeMenu.addButton(button);
		this.morphMenu.getEl().appendTo(button.node);
		this.morphButton = button;
	},

	showMorphMenu: function () {
		this.morphMenu.show(this.morphButton.node);
		this._morphMenuShown = true;
	},

	hideMorphMenu: function () {
		this.morphMenu.hide();
		this._morphMenuShown = false;
	},

	toggleMorphMenu: function () {
		if (this._morphMenuShown)
			this.hideMorphMenu();
		else
			this.showMorphMenu();
	},

	onSelectionChanged: function (event) {
		var elements = event.elements;

		this.hideShapeMenu();
		this.hideMorphMenu();

		if (this.currentShapes.inspect() !== elements.inspect()) {
			this.currentShapes = elements;
			this.resetElements = true;

			this.showShapeMenu();
		} else {
			this.showShapeMenu(true)
		}

	},

	/**
	 * Show button for morphing the selected shape into another stencil
	 */
	showMorphButton: function (elements) {

		if (elements.length != 1) return;

		var possibleMorphs = this.facade.getRules().morphStencils({ stencil: elements[0].getStencil() });
		possibleMorphs = possibleMorphs.select(function (morph) {
			if (elements[0].getStencil().type() === "node") {
				//check containment rules
				return this.facade.getRules().canContain({ containingShape: elements[0].parent, containedStencil: morph });
			} else {
				//check connect rules
				return this.facade.getRules().canConnect({
					sourceShape: elements[0].dockers.first().getDockedShape(),
					edgeStencil: morph,
					targetShape: elements[0].dockers.last().getDockedShape()
				});
			}
		}.bind(this));
		if (possibleMorphs.size() <= 1) return; // if morphing to other stencils is not possible, don't show button

		this.morphMenu.removeAll();

		// populate morph menu with the possible morph stencils ordered by their position
		possibleMorphs = possibleMorphs.sortBy(function (stencil) { return stencil.position(); });
		possibleMorphs.each((function (morph) {
			var menuItem = new Ext.menu.Item({
				text: morph.title(),
				icon: morph.icon(),
				disabled: morph.id() == elements[0].getStencil().id(),
				disabledClass: ORYX.CONFIG.MORPHITEM_DISABLED,
				handler: (function () { this.morphShape(elements[0], morph); }).bind(this)
			});
			this.morphMenu.add(menuItem);
		}).bind(this));

		this.morphButton.prepareToShow();

	},

	/**
	 * Show buttons for creating following shapes
	 */
	showStencilButtons: function (elements) {

		if (elements.length != 1) return;

		//TODO temporaere nutzung des stencilsets
		var sset = this.facade.getStencilSets()[elements[0].getStencil().namespace()];

		// Get all available edges
		var edges = this.facade.getRules().outgoingEdgeStencils({ canvas: this.facade.getCanvas(), sourceShape: elements[0] });

		// And find all targets for each Edge
		var targets = new Array();
		var addedEdges = new Array();

		var isMorphing = this.facade.getRules().containsMorphingRules();

		edges.each((function (edge) {

			if (isMorphing) {
				if (this.baseMorphStencils.include(edge)) {
					var shallAppear = true;
				} else {

					// if edge is member of a morph groups where none of the base morphs is in the outgoing edges
					// we want to display the button (but only for the first one)

					var possibleMorphs = this.facade.getRules().morphStencils({ stencil: edge });

					var shallAppear = !possibleMorphs.any((function (morphStencil) {
						if (this.baseMorphStencils.include(morphStencil) && edges.include(morphStencil)) return true;
						return addedEdges.include(morphStencil);
					}).bind(this));

				}
			}
			if (shallAppear || !isMorphing) {
				if (this.createdButtons[edge.namespace() + edge.type() + edge.id()])
					this.createdButtons[edge.namespace() + edge.type() + edge.id()].prepareToShow();
				addedEdges.push(edge);
			}

			// get all targets for this edge
			targets = targets.concat(this.facade.getRules().targetStencils(
				{ canvas: this.facade.getCanvas(), sourceShape: elements[0], edgeStencil: edge }));

		}).bind(this));

		targets.uniq();

		var addedTargets = new Array();
		// Iterate all possible target 
		targets.each((function (target) {

			if (isMorphing) {

				// continue with next target stencil
				if (target.type() === "edge") return;

				// continue when stencil should not shown in the shape menu
				if (!this.facade.getRules().showInShapeMenu(target)) return

				// if target is not a base morph 
				if (!this.baseMorphStencils.include(target)) {

					// if target is member of a morph groups where none of the base morphs is in the targets
					// we want to display the button (but only for the first one)

					var possibleMorphs = this.facade.getRules().morphStencils({ stencil: target });
					if (possibleMorphs.size() == 0) return; // continue with next target

					var baseMorphInTargets = possibleMorphs.any((function (morphStencil) {
						if (this.baseMorphStencils.include(morphStencil) && targets.include(morphStencil)) return true;
						return addedTargets.include(morphStencil);
					}).bind(this));

					if (baseMorphInTargets) return; // continue with next target
				}
			}

			// if this is reached the button shall appear in the shape menu:
			if (this.createdButtons[target.namespace() + target.type() + target.id()])
				this.createdButtons[target.namespace() + target.type() + target.id()].prepareToShow();
			addedTargets.push(target);

		}).bind(this));

	},


	beforeDragOver: function (dragZone, target, event) {

		if (this.shapeMenu.isVisible) {
			this.hideShapeMenu();
		}

		var coord = this.facade.eventCoordinates(event.browserEvent);
		var aShapes = this.facade.getCanvas().getAbstractShapesAtPosition(coord);

		if (aShapes.length <= 0) { return false; }

		var el = aShapes.last();

		if (this._lastOverElement == el) {

			return false;

		} else {
			// check containment rules
			var option = Ext.dd.Registry.getHandle(target.DDM.currentTarget);

			// revert to original options if these were modified
			if (option.backupOptions) {
				for (key in option.backupOptions) {
					option[key] = option.backupOptions[key];
				}
				delete option.backupOptions;
			}

			var stencilSet = this.facade.getStencilSets()[option.namespace];

			var stencil = stencilSet.stencil(option.type);

			var candidate = aShapes.last();

			if (stencil.type() === "node") {
				//check containment rules
				var canContain = this.facade.getRules().canContain({ containingShape: candidate, containedStencil: stencil });

				// if not canContain, try to find a morph which can be contained
				if (!canContain) {
					var possibleMorphs = this.facade.getRules().morphStencils({ stencil: stencil });
					for (var i = 0; i < possibleMorphs.size(); i++) {
						canContain = this.facade.getRules().canContain({
							containingShape: candidate,
							containedStencil: possibleMorphs[i]
						});
						if (canContain) {
							option.backupOptions = Object.clone(option);
							option.type = possibleMorphs[i].id();
							option.namespace = possibleMorphs[i].namespace();
							break;
						}
					}
				}

				this._currentReference = canContain ? candidate : undefined;


			} else { //Edge

				var curCan = candidate, orgCan = candidate;
				var canConnect = false;
				while (!canConnect && curCan && !(curCan instanceof ORYX.Core.Canvas)) {
					candidate = curCan;
					//check connection rules
					canConnect = this.facade.getRules().canConnect({
						sourceShape: this.currentShapes.first(),
						edgeStencil: stencil,
						targetShape: curCan
					});
					curCan = curCan.parent;
				}

				// if not canConnect, try to find a morph which can be connected
				if (!canConnect) {

					candidate = orgCan;
					var possibleMorphs = this.facade.getRules().morphStencils({ stencil: stencil });
					for (var i = 0; i < possibleMorphs.size(); i++) {
						var curCan = candidate;
						var canConnect = false;
						while (!canConnect && curCan && !(curCan instanceof ORYX.Core.Canvas)) {
							candidate = curCan;
							//check connection rules
							canConnect = this.facade.getRules().canConnect({
								sourceShape: this.currentShapes.first(),
								edgeStencil: possibleMorphs[i],
								targetShape: curCan
							});
							curCan = curCan.parent;
						}
						if (canConnect) {
							option.backupOptions = Object.clone(option);
							option.type = possibleMorphs[i].id();
							option.namespace = possibleMorphs[i].namespace();
							break;
						} else {
							candidate = orgCan;
						}
					}
				}

				this._currentReference = canConnect ? candidate : undefined;

			}

			this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
				highlightId: 'shapeMenu',
				elements: [candidate],
				color: this._currentReference ? ORYX.CONFIG.SELECTION_VALID_COLOR : ORYX.CONFIG.SELECTION_INVALID_COLOR
			});

			var pr = dragZone.getProxy();
			pr.setStatus(this._currentReference ? pr.dropAllowed : pr.dropNotAllowed);
			pr.sync();

		}

		this._lastOverElement = el;

		return false;
	},

	afterDragging: function (dragZone, target, event) {

		if (!(this.currentShapes instanceof Array) || this.currentShapes.length <= 0) {
			return;
		}
		var sourceShape = this.currentShapes;

		this._lastOverElement = undefined;

		// Hide the highlighting
		this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'shapeMenu' });

		// Check if drop is allowed
		var proxy = dragZone.getProxy()
		if (proxy.dropStatus == proxy.dropNotAllowed) { return this.facade.updateSelection(); }

		// Check if there is a current Parent
		if (!this._currentReference) { return }

		var option = Ext.dd.Registry.getHandle(target.DDM.currentTarget);
		option['parent'] = this._currentReference;

		var xy = event.getXY();
		var pos = { x: xy[0], y: xy[1] };

		var a = this.facade.getCanvas().node.getScreenCTM();
		// Correcting the UpperLeft-Offset
		pos.x -= a.e; pos.y -= a.f;
		// Correcting the Zoom-Faktor
		pos.x /= a.a; pos.y /= a.d;
		// Correcting the ScrollOffset
		pos.x -= document.documentElement.scrollLeft;
		pos.y -= document.documentElement.scrollTop;

		var parentAbs = this._currentReference.absoluteXY();
		pos.x -= parentAbs.x;
		pos.y -= parentAbs.y;

		// If the ctrl key is not pressed, 
		// snapp the new shape to the center 
		// if it is near to the center of the other shape
		if (!event.ctrlKey) {
			// Get the center of the shape
			var cShape = this.currentShapes[0].bounds.center();
			// Snapp +-20 Pixel horizontal to the center 
			if (20 > Math.abs(cShape.x - pos.x)) {
				pos.x = cShape.x;
			}
			// Snapp +-20 Pixel vertical to the center 
			if (20 > Math.abs(cShape.y - pos.y)) {
				pos.y = cShape.y;
			}
		}

		option['position'] = pos;
		option['connectedShape'] = this.currentShapes[0];
		if (option['connectingType']) {
			var stencilset = this.facade.getStencilSets()[option.namespace];
			var containedStencil = stencilset.stencil(option.type);
			var args = { sourceShape: this.currentShapes[0], targetStencil: containedStencil };
			option['connectingType'] = this.facade.getRules().connectMorph(args).id();
		}

		if (ORYX.CONFIG.SHAPEMENU_DISABLE_CONNECTED_EDGE === true) {
			delete option['connectingType'];
		}

		var command = new ORYX.Plugins.ShapeMenuPlugin.CreateCommand(Object.clone(option), this._currentReference, pos, this);

		this.facade.executeCommands([command]);

		// Inform about completed Drag 
		this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_SHAPE_MENU_CLOSE, source: sourceShape, destination: this.currentShapes });

		// revert to original options if these were modified
		if (option.backupOptions) {
			for (key in option.backupOptions) {
				option[key] = option.backupOptions[key];
			}
			delete option.backupOptions;
		}

		this._currentReference = undefined;
	},

	newShape: function (option, event) {
		var stencilset = this.facade.getStencilSets()[option.namespace];
		var containedStencil = stencilset.stencil(option.type);

		if (this.facade.getRules().canContain({
			containingShape: this.currentShapes.first().parent,
			"containedStencil": containedStencil
		})) {

			option['connectedShape'] = this.currentShapes[0];
			option['parent'] = this.currentShapes.first().parent;
			option['containedStencil'] = containedStencil;

			var args = { sourceShape: this.currentShapes[0], targetStencil: containedStencil };
			var targetStencil = this.facade.getRules().connectMorph(args);
			if (!targetStencil) { return }// Check if there can be a target shape
			option['connectingType'] = targetStencil.id();

			if (ORYX.CONFIG.SHAPEMENU_DISABLE_CONNECTED_EDGE === true) {
				delete option['connectingType'];
			}

			var command = new ORYX.Plugins.ShapeMenuPlugin.CreateCommand(option, undefined, undefined, this);

			this.facade.executeCommands([command]);
		}
	},

	/**
	 * Morph a shape to a new stencil
	 * {Command implemented}
	 * @param {Shape} shape
	 * @param {Stencil} stencil
	 */
	morphShape: function (shape, stencil) {

		var MorphTo = ORYX.Core.Command.extend({
			construct: function (shape, stencil, facade) {
				this.shape = shape;
				this.stencil = stencil;
				this.facade = facade;
			},
			execute: function () {

				var shape = this.shape;
				var stencil = this.stencil;
				var resourceId = shape.resourceId;

				// Serialize all attributes
				var serialized = shape.serialize();
				stencil.properties().each((function (prop) {
					if (prop.readonly()) {
						serialized = serialized.reject(function (serProp) {
							return serProp.name == prop.id();
						});
					}
				}).bind(this));

				// Get shape if already created, otherwise create a new shape
				if (this.newShape) {
					newShape = this.newShape;
					this.facade.getCanvas().add(newShape);
				} else {
					newShape = this.facade.createShape({
						type: stencil.id(),
						namespace: stencil.namespace(),
						resourceId: resourceId
					});
				}

				// calculate new bounds using old shape's upperLeft and new shape's width/height
				var boundsObj = serialized.find(function (serProp) {
					return (serProp.prefix === "oryx" && serProp.name === "bounds");
				});

				var changedBounds = null;

				if (!this.facade.getRules().preserveBounds(shape.getStencil())) {

					var bounds = boundsObj.value.split(",");
					if (parseInt(bounds[0], 10) > parseInt(bounds[2], 10)) { // if lowerRight comes first, swap array items
						var tmp = bounds[0];
						bounds[0] = bounds[2];
						bounds[2] = tmp;
						tmp = bounds[1];
						bounds[1] = bounds[3];
						bounds[3] = tmp;
					}
					bounds[2] = parseInt(bounds[0], 10) + newShape.bounds.width();
					bounds[3] = parseInt(bounds[1], 10) + newShape.bounds.height();
					boundsObj.value = bounds.join(",");

				} else {

					var height = shape.bounds.height();
					var width = shape.bounds.width();

					// consider the minimum and maximum size of
					// the new shape

					if (newShape.minimumSize) {
						if (shape.bounds.height() < newShape.minimumSize.height) {
							height = newShape.minimumSize.height;
						}


						if (shape.bounds.width() < newShape.minimumSize.width) {
							width = newShape.minimumSize.width;
						}
					}

					if (newShape.maximumSize) {
						if (shape.bounds.height() > newShape.maximumSize.height) {
							height = newShape.maximumSize.height;
						}

						if (shape.bounds.width() > newShape.maximumSize.width) {
							width = newShape.maximumSize.width;
						}
					}

					changedBounds = {
						a: {
							x: shape.bounds.a.x,
							y: shape.bounds.a.y
						},
						b: {
							x: shape.bounds.a.x + width,
							y: shape.bounds.a.y + height
						}
					};

				}

				var oPos = shape.bounds.center();
				if (changedBounds !== null) {
					newShape.bounds.set(changedBounds);
				}

				// Set all related dockers
				this.setRelatedDockers(shape, newShape);

				// store DOM position of old shape
				var parentNode = shape.node.parentNode;
				var nextSibling = shape.node.nextSibling;

				// Delete the old shape
				this.facade.deleteShape(shape);

				// Deserialize the new shape - Set all attributes
				newShape.deserialize(serialized);
				/*
				 * Change color to default if unchanged
				 * 23.04.2010
				 */
				if (shape.getStencil().property("oryx-bgcolor")
					&& shape.properties["oryx-bgcolor"]
					&& shape.getStencil().property("oryx-bgcolor").value().toUpperCase() == shape.properties["oryx-bgcolor"].toUpperCase()) {
					if (newShape.getStencil().property("oryx-bgcolor")) {
						newShape.setProperty("oryx-bgcolor", newShape.getStencil().property("oryx-bgcolor").value());
					}
				}
				if (changedBounds !== null) {
					newShape.bounds.set(changedBounds);
				}

				if (newShape.getStencil().type() === "edge" || (newShape.dockers.length == 0 || !newShape.dockers[0].getDockedShape())) {
					newShape.bounds.centerMoveTo(oPos);
				}

				if (newShape.getStencil().type() === "node" && (newShape.dockers.length == 0 || !newShape.dockers[0].getDockedShape())) {
					this.setRelatedDockers(newShape, newShape);

				}

				// place at the DOM position of the old shape
				if (nextSibling) parentNode.insertBefore(newShape.node, nextSibling);
				else parentNode.appendChild(newShape.node);

				// Set selection
				this.facade.setSelection([newShape]);
				this.facade.getCanvas().update();
				this.facade.updateSelection();
				this.newShape = newShape;

			},
			rollback: function () {

				if (!this.shape || !this.newShape || !this.newShape.parent) { return }

				// Append shape to the parent
				this.newShape.parent.add(this.shape);
				// Set dockers
				this.setRelatedDockers(this.newShape, this.shape);
				// Delete new shape
				this.facade.deleteShape(this.newShape);
				// Set selection
				this.facade.setSelection([this.shape]);
				// Update
				this.facade.getCanvas().update();
				this.facade.updateSelection();
			},

			/**
			 * Set all incoming and outgoing edges from the shape to the new shape
			 * @param {Shape} shape
			 * @param {Shape} newShape
			 */
			setRelatedDockers: function (shape, newShape) {

				if (shape.getStencil().type() === "node") {

					(shape.incoming || []).concat(shape.outgoing || [])
						.each(function (i) {
							i.dockers.each(function (docker) {
								if (docker.getDockedShape() == shape) {
									var rPoint = Object.clone(docker.referencePoint);
									// Move reference point per percent

									var rPointNew = {
										x: rPoint.x * newShape.bounds.width() / shape.bounds.width(),
										y: rPoint.y * newShape.bounds.height() / shape.bounds.height()
									};

									docker.setDockedShape(newShape);
									// Set reference point and center to new position
									docker.setReferencePoint(rPointNew);
									if (i instanceof ORYX.Core.Edge) {
										docker.bounds.centerMoveTo(rPointNew);
									} else {
										var absXY = shape.absoluteXY();
										docker.bounds.centerMoveTo({ x: rPointNew.x + absXY.x, y: rPointNew.y + absXY.y });
										//docker.bounds.moveBy({x:rPointNew.x-rPoint.x, y:rPointNew.y-rPoint.y});
									}
								}
							});
						});

					// for attached events
					if (shape.dockers.length > 0 && shape.dockers.first().getDockedShape()) {
						newShape.dockers.first().setDockedShape(shape.dockers.first().getDockedShape());
						newShape.dockers.first().setReferencePoint(Object.clone(shape.dockers.first().referencePoint));
					}

				} else { // is edge
					newShape.dockers.first().setDockedShape(shape.dockers.first().getDockedShape());
					newShape.dockers.first().setReferencePoint(shape.dockers.first().referencePoint);
					newShape.dockers.last().setDockedShape(shape.dockers.last().getDockedShape());
					newShape.dockers.last().setReferencePoint(shape.dockers.last().referencePoint);
				}
			}
		});

		// Create and execute command (for undo/redo)			
		var command = new MorphTo(shape, stencil, this.facade);
		this.facade.executeCommands([command]);
	}
}
ORYX.Plugins.ShapeMenuPlugin = ORYX.Plugins.AbstractPlugin.extend(ORYX.Plugins.ShapeMenuPlugin);

ORYX.Plugins.ShapeMenu = {

	/***
	 * Constructor.
	 */
	construct: function (parentNode) {

		this.bounds = undefined;
		this.shapes = undefined;
		this.buttons = [];
		this.isVisible = false;

		this.node = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", $(parentNode),
			['div', { id: ORYX.Editor.provideId(), 'class': 'Oryx_ShapeMenu' }]);

		this.alignContainers = new Hash();
		this.numberOfButtonsPerLevel = new Hash();
	},

	addButton: function (button) {
		this.buttons.push(button);
		// lazy grafting of the align containers
		if (!this.alignContainers[button.align]) {
			this.alignContainers[button.align] = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", this.node,
				['div', { 'class': button.align }]);
			this.node.appendChild(this.alignContainers[button.align]);

			// add event listeners for hover effect
			var onBubble = false;
			this.alignContainers[button.align].addEventListener(ORYX.CONFIG.EVENT_MOUSEOVER, this.hoverAlignContainer.bind(this, button.align), onBubble);
			this.alignContainers[button.align].addEventListener(ORYX.CONFIG.EVENT_MOUSEOUT, this.resetAlignContainer.bind(this, button.align), onBubble);
			this.alignContainers[button.align].addEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this.hoverAlignContainer.bind(this, button.align), onBubble);
		}
		this.alignContainers[button.align].appendChild(button.node);
	},

	deleteButton: function (button) {
		this.buttons = this.buttons.without(button);
		this.node.removeChild(button.node);
	},

	removeAllButtons: function () {
		var me = this;
		this.buttons.each(function (value) {
			if (value.node && value.node.parentNode)
				value.node.parentNode.removeChild(value.node);
		});
		this.buttons = [];
	},

	closeAllButtons: function () {
		this.buttons.each(function (value) { value.prepareToHide() });
		this.isVisible = false;
	},


	/**
	 * Show the shape menu
	 */
	show: function (shapes) {

		//shapes = (shapes||[]).findAll(function(r){ return r && r.node && r.node.parent });

		if (shapes.length <= 0)
			return

		this.shapes = shapes;

		var newBounds = undefined;
		var tmpBounds = undefined;

		this.shapes.each(function (value) {
			var a = value.node.getScreenCTM();
			var upL = value.absoluteXY();
			a.e = a.a * upL.x;
			a.f = a.d * upL.y;
			tmpBounds = new ORYX.Core.Bounds(a.e, a.f, a.e + a.a * value.bounds.width(), a.f + a.d * value.bounds.height());

			/*if(value instanceof ORYX.Core.Edge) {
				tmpBounds.moveBy(value.bounds.upperLeft())
			}*/

			if (!newBounds)
				newBounds = tmpBounds
			else
				newBounds.include(tmpBounds);

		});

		this.bounds = newBounds;
		//this.bounds.moveBy({x:document.documentElement.scrollLeft, y:document.documentElement.scrollTop});

		var bounds = this.bounds;

		var a = this.bounds.upperLeft();

		var left = 0,
			leftButtonGroup = 0;
		var top = 0,
			topButtonGroup = 0;
		var bottom = 0,
			bottomButtonGroup;
		var right = 0
		rightButtonGroup = 0;
		var size = 22;

		this.getWillShowButtons().sortBy(function (button) {
			return button.group;
		});

		this.getWillShowButtons().each(function (button) {

			var numOfButtonsPerLevel = this.getNumberOfButtonsPerLevel(button.align);

			if (button.align == ORYX.CONFIG.SHAPEMENU_LEFT) {
				// vertical levels
				if (button.group != leftButtonGroup) {
					left = 0;
					leftButtonGroup = button.group;
				}
				var x = Math.floor(left / numOfButtonsPerLevel)
				var y = left % numOfButtonsPerLevel;

				button.setLevel(x);

				button.setPosition(a.x - 5 - (x + 1) * size,
					a.y + numOfButtonsPerLevel * button.group * size + button.group * 0.3 * size + y * size);

				//button.setPosition(a.x-22, a.y+left*size);
				left++;
			} else if (button.align == ORYX.CONFIG.SHAPEMENU_TOP) {
				// horizontal levels
				if (button.group != topButtonGroup) {
					top = 0;
					topButtonGroup = button.group;
				}
				var x = top % numOfButtonsPerLevel;
				var y = Math.floor(top / numOfButtonsPerLevel);

				button.setLevel(y);

				button.setPosition(a.x + numOfButtonsPerLevel * button.group * size + button.group * 0.3 * size + x * size,
					a.y - 5 - (y + 1) * size);
				top++;
			} else if (button.align == ORYX.CONFIG.SHAPEMENU_BOTTOM) {
				// horizontal levels
				if (button.group != bottomButtonGroup) {
					bottom = 0;
					bottomButtonGroup = button.group;
				}
				var x = bottom % numOfButtonsPerLevel;
				var y = Math.floor(bottom / numOfButtonsPerLevel);

				button.setLevel(y);

				button.setPosition(a.x + numOfButtonsPerLevel * button.group * size + button.group * 0.3 * size + x * size,
					a.y + bounds.height() + 5 + y * size);
				bottom++;
			} else {
				// vertical levels
				if (button.group != rightButtonGroup) {
					right = 0;
					rightButtonGroup = button.group;
				}
				var x = Math.floor(right / numOfButtonsPerLevel)
				var y = right % numOfButtonsPerLevel;

				button.setLevel(x);

				button.setPosition(a.x + bounds.width() + 5 + x * size,
					a.y + numOfButtonsPerLevel * button.group * size + button.group * 0.3 * size + y * size - 5);
				right++;
			}

			button.show();
		}.bind(this));
		this.isVisible = true;

	},

	/**
	 * Hide the shape menu
	 */
	hide: function () {

		this.buttons.each(function (button) {
			button.hide();
		});

		this.isVisible = false;
		//this.bounds = undefined;
		//this.shape = undefined;
	},

	hoverAlignContainer: function (align, evt) {
		this.buttons.each(function (button) {
			if (button.align == align) button.showOpaque();
		});
	},

	resetAlignContainer: function (align, evt) {
		this.buttons.each(function (button) {
			if (button.align == align) button.showTransparent();
		});
	},

	isHover: function () {
		return this.buttons.any(function (value) {
			return value.isHover();
		});
	},

	getWillShowButtons: function () {
		return this.buttons.findAll(function (value) { return value.willShow });
	},

	/**
	 * Returns a set on buttons for that align value
	 * @params {String} align
	 * @params {String} group
	 */
	getButtons: function (align, group) {
		return this.getWillShowButtons().findAll(function (b) { return b.align == align && (group === undefined || b.group == group) })
	},

	/**
	 * Set the number of buttons to display on each level of the shape menu in the specified align group.
	 * Example: setNumberOfButtonsPerLevel(ORYX.CONFIG.SHAPEMENU_RIGHT, 2) causes that the buttons of the right align group 
	 * will be rendered in 2 rows.
	 */
	setNumberOfButtonsPerLevel: function (align, number) {
		this.numberOfButtonsPerLevel[align] = number;
	},

	/**
	 * Returns the number of buttons to display on each level of the shape menu in the specified align group.
	 * Default value is 1
	 */
	getNumberOfButtonsPerLevel: function (align) {
		if (this.numberOfButtonsPerLevel[align])
			return Math.min(this.getButtons(align, 0).length, this.numberOfButtonsPerLevel[align]);
		else
			return 1;
	}

}
ORYX.Plugins.ShapeMenu = Clazz.extend(ORYX.Plugins.ShapeMenu);

ORYX.Plugins.ShapeMenuButton = {

	/**
	 * Constructor
	 * @param option A key map specifying the configuration options:
	 * 					id: 	(String) The id of the parent DOM element for the new button
	 * 					icon: 	(String) The url to the icon of the button
	 * 					msg:	(String) A tooltip message
	 * 					caption:(String) The caption of the button (attention: button width > 22, only set for single column button layouts)
	 * 					align:	(String) The direction in which the button is aligned
	 * 					group: 	(Integer) The button group in the specified alignment 
	 * 							(buttons in the same group will be aligned side by side)
	 * 					callback:		(Function) A callback that is executed when the button is clicked
	 * 					dragcallback:	(Function) A callback that is executed when the button is dragged
	 * 					hovercallback:	(Function) A callback that is executed when the button is hovered
	 * 					resetcallback:	(Function) A callback that is executed when the button is reset
	 * 					arguments:		(Array) An argument array to pass to the callback functions
	 */
	construct: function (option) {

		if (option) {
			this.option = option;
			if (!this.option.arguments)
				this.option.arguments = [];
		} else {
			//TODO error
		}

		this.parentId = this.option.id ? this.option.id : null;

		// graft the button.
		var buttonClassName = this.option.caption ? "Oryx_button_with_caption" : "Oryx_button";
		this.node = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", $(this.parentId),
			['div', { 'class': buttonClassName }]);

		var imgOptions = { src: this.option.icon };
		if (this.option.msg) {
			imgOptions.title = this.option.msg;
		}

		// graft and update icon (not in grafting for ns reasons).
		//TODO Enrich graft()-function to do this in one of the above steps.
		if (this.option.icon)
			ORYX.Editor.graft("http://www.w3.org/1999/xhtml", this.node,
				['img', imgOptions]);

		if (this.option.caption) {
			var captionNode = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", this.node, ['span']);
			ORYX.Editor.graft("http://www.w3.org/1999/xhtml", captionNode, this.option.caption);
		}

		var onBubble = false;

		this.node.addEventListener(ORYX.CONFIG.EVENT_MOUSEOVER, this.hover.bind(this), onBubble);
		this.node.addEventListener(ORYX.CONFIG.EVENT_MOUSEOUT, this.reset.bind(this), onBubble);
		this.node.addEventListener(ORYX.CONFIG.EVENT_MOUSEDOWN, this.activate.bind(this), onBubble);
		this.node.addEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this.hover.bind(this), onBubble);
		this.node.addEventListener('click', this.trigger.bind(this), onBubble);
		this.node.addEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this.move.bind(this), onBubble);

		this.align = this.option.align ? this.option.align : ORYX.CONFIG.SHAPEMENU_RIGHT;
		this.group = this.option.group ? this.option.group : 0;

		this.hide();

		this.dragStart = false;
		this.isVisible = false;
		this.willShow = false;
		this.resetTimer;
	},

	hide: function () {
		this.node.style.display = "none";
		this.isVisible = false;
	},

	show: function () {
		this.node.style.display = "";
		this.node.style.opacity = this.opacity;
		this.isVisible = true;
	},

	showOpaque: function () {
		this.node.style.opacity = 1.0;
	},

	showTransparent: function () {
		this.node.style.opacity = this.opacity;
	},

	prepareToShow: function () {
		this.willShow = true;
	},

	prepareToHide: function () {
		this.willShow = false;
		this.hide();
	},

	setPosition: function (x, y) {
		this.node.style.left = x + "px";
		this.node.style.top = y + "px";
	},

	setLevel: function (level) {
		if (level == 0) this.opacity = 0.5;
		else if (level == 1) this.opacity = 0.2;
		//else if(level==2) this.opacity = 0.1;
		else this.opacity = 0.0;
	},

	setChildWidth: function (width) {
		this.childNode.style.width = width + "px";
	},

	reset: function (evt) {
		// Delete the timeout for hiding
		window.clearTimeout(this.resetTimer)
		this.resetTimer = window.setTimeout(this.doReset.bind(this), 100)

		if (this.option.resetcallback) {
			this.option.arguments.push(evt);
			var state = this.option.resetcallback.apply(this, this.option.arguments);
			this.option.arguments.remove(evt);
		}
	},

	doReset: function () {

		if (this.node.hasClassName('Oryx_down'))
			this.node.removeClassName('Oryx_down');

		if (this.node.hasClassName('Oryx_hover'))
			this.node.removeClassName('Oryx_hover');

	},

	activate: function (evt) {
		this.node.addClassName('Oryx_down');
		//Event.stop(evt);
		this.dragStart = true;
	},

	isHover: function () {
		return this.node.hasClassName('Oryx_hover') ? true : false;
	},

	hover: function (evt) {
		// Delete the timeout for hiding
		window.clearTimeout(this.resetTimer)
		this.resetTimer = null;

		this.node.addClassName('Oryx_hover');
		this.dragStart = false;

		if (this.option.hovercallback) {
			this.option.arguments.push(evt);
			var state = this.option.hovercallback.apply(this, this.option.arguments);
			this.option.arguments.remove(evt);
		}
	},

	move: function (evt) {
		if (this.dragStart && this.option.dragcallback) {
			this.option.arguments.push(evt);
			var state = this.option.dragcallback.apply(this, this.option.arguments);
			this.option.arguments.remove(evt);
		}
	},

	trigger: function (evt) {
		if (this.option.callback) {
			//Event.stop(evt);
			this.option.arguments.push(evt);
			var state = this.option.callback.apply(this, this.option.arguments);
			this.option.arguments.remove(evt);
		}
		this.dragStart = false;
	},

	toString: function () {
		return "HTML-Button " + this.id;
	}
}
ORYX.Plugins.ShapeMenuButton = Clazz.extend(ORYX.Plugins.ShapeMenuButton);

//create command for undo/redo
ORYX.Plugins.ShapeMenuPlugin.CreateCommand = ORYX.Core.Command.extend({
	construct: function (option, currentReference, position, plugin) {
		this.option = option;
		this.currentReference = currentReference;
		this.position = position;
		this.plugin = plugin;
		this.shape;
		this.edge;
		this.targetRefPos;
		this.sourceRefPos;
		/*
		 * clone options parameters
		 */
		this.connectedShape = option.connectedShape;
		this.connectingType = option.connectingType;
		this.namespace = option.namespace;
		this.type = option.type;
		this.containedStencil = option.containedStencil;
		this.parent = option.parent;
		this.currentReference = currentReference;
		this.shapeOptions = option.shapeOptions;
	},
	execute: function () {

		var resume = false;

		if (this.shape) {
			if (this.shape instanceof ORYX.Core.Node) {
				this.parent.add(this.shape);
				if (this.edge) {
					this.plugin.facade.getCanvas().add(this.edge);
					this.edge.dockers.first().setDockedShape(this.connectedShape);
					this.edge.dockers.first().setReferencePoint(this.sourceRefPos);
					this.edge.dockers.last().setDockedShape(this.shape);
					this.edge.dockers.last().setReferencePoint(this.targetRefPos);
				}

				this.plugin.facade.setSelection([this.shape]);

			} else if (this.shape instanceof ORYX.Core.Edge) {
				this.plugin.facade.getCanvas().add(this.shape);
				this.shape.dockers.first().setDockedShape(this.connectedShape);
				this.shape.dockers.first().setReferencePoint(this.sourceRefPos);
			}
			resume = true;
		}
		else {
			this.shape = this.plugin.facade.createShape(this.option);
			this.edge = (!(this.shape instanceof ORYX.Core.Edge)) ? this.shape.getIncomingShapes().first() : undefined;
		}

		if (this.currentReference && this.position) {

			if (this.shape instanceof ORYX.Core.Edge) {

				if (!(this.currentReference instanceof ORYX.Core.Canvas)) {
					this.shape.dockers.last().setDockedShape(this.currentReference);

					// @deprecated It now uses simply the midpoint
					var upL = this.currentReference.absoluteXY();
					var refPos = {
						x: this.position.x - upL.x,
						y: this.position.y - upL.y
					};

					this.shape.dockers.last().setReferencePoint(this.currentReference.bounds.midPoint());
				}
				else {
					this.shape.dockers.last().bounds.centerMoveTo(this.position);
					//this.shape.dockers.last().update();
				}
				this.sourceRefPos = this.shape.dockers.first().referencePoint;
				this.targetRefPos = this.shape.dockers.last().referencePoint;

			} else if (this.edge) {
				this.sourceRefPos = this.edge.dockers.first().referencePoint;
				this.targetRefPos = this.edge.dockers.last().referencePoint;
			}
		} else {
			var containedStencil = this.containedStencil;
			var connectedShape = this.connectedShape;
			var bc = connectedShape.bounds;
			var bs = this.shape.bounds;

			var pos = bc.center();
			if (containedStencil.defaultAlign() === "north") {
				pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.height() / 2);
			} else if (containedStencil.defaultAlign() === "northeast") {
				pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width() / 2);
				pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height() / 2);
			} else if (containedStencil.defaultAlign() === "southeast") {
				pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width() / 2);
				pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height() / 2);
			} else if (containedStencil.defaultAlign() === "south") {
				pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.height() / 2);
			} else if (containedStencil.defaultAlign() === "southwest") {
				pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width() / 2);
				pos.y += (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height() / 2);
			} else if (containedStencil.defaultAlign() === "west") {
				pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.width() / 2);
			} else if (containedStencil.defaultAlign() === "northwest") {
				pos.x -= (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.width() / 2);
				pos.y -= (bc.height() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET_CORNER + (bs.height() / 2);
			} else {
				pos.x += (bc.width() / 2) + ORYX.CONFIG.SHAPEMENU_CREATE_OFFSET + (bs.width() / 2);
			}

			// Move shape to the new position
			this.shape.bounds.centerMoveTo(pos);

			// Move all dockers of a node to the position
			if (this.shape instanceof ORYX.Core.Node) {
				(this.shape.dockers || []).each(function (docker) {
					docker.bounds.centerMoveTo(pos);
				})
			}

			//this.shape.update();
			this.position = pos;

			if (this.edge) {
				this.sourceRefPos = this.edge.dockers.first().referencePoint;
				this.targetRefPos = this.edge.dockers.last().referencePoint;
			}
		}

		this.plugin.facade.getCanvas().update();
		this.plugin.facade.updateSelection();

		if (!resume) {
			// If there is a connected shape
			if (this.edge) {
				// Try to layout it
				this.plugin.doLayout(this.edge);
			} else if (this.shape instanceof ORYX.Core.Edge) {
				// Try to layout it
				this.plugin.doLayout(this.shape);
			}
		}

	},
	rollback: function () {
		this.plugin.facade.deleteShape(this.shape);
		if (this.edge) {
			this.plugin.facade.deleteShape(this.edge);
		}
		//this.currentParent.update();
		this.plugin.facade.setSelection(this.plugin.facade.getSelection().without(this.shape, this.edge));
	}
});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/


if (!ORYX.Plugins) {
	ORYX.Plugins = new Object();
}

ORYX.Plugins.ShapeRepository = {

	facade: undefined,

	construct: function (facade) {
		this.facade = facade;
		this._currentParent;
		this._canContain = undefined;
		this._canAttach = undefined;

		this.shapeList = new Ext.tree.TreeNode({

		});

		var panel = new Ext.tree.TreePanel({
			cls: 'shaperepository',
			loader: new Ext.tree.TreeLoader(),
			root: this.shapeList,
			autoScroll: true,
			rootVisible: false,
			lines: false,
			anchors: '0, -30'
		});
		var region = this.facade.addToRegion("west", panel, ORYX.I18N.ShapeRepository.title);


		// Create a Drag-Zone for Drag'n'Drop
		var DragZone = new Ext.dd.DragZone(this.shapeList.getUI().getEl(), { shadow: !Ext.isMac });
		DragZone.afterDragDrop = this.drop.bind(this, DragZone);
		DragZone.beforeDragOver = this.beforeDragOver.bind(this, DragZone);
		DragZone.beforeDragEnter = function () { this._lastOverElement = false; return true }.bind(this);

		// Load all Stencilssets
		this.setStencilSets();

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_STENCIL_SET_LOADED, this.setStencilSets.bind(this));

	},


	/**
	 * Load all stencilsets in the shaperepository
	 */
	setStencilSets: function () {
		// Remove all childs
		var child = this.shapeList.firstChild;
		while (child) {
			this.shapeList.removeChild(child);
			child = this.shapeList.firstChild;
		}

		ORYX.Log.info("stencilsets " + this.facade.getStencilSets());

		// Go thru all Stencilsets and stencils
		this.facade.getStencilSets().values().each((function (sset) {

			// For each Stencilset create and add a new Tree-Node
			var stencilSetNode;

			var typeTitle = sset.title();

			this.shapeList.appendChild(stencilSetNode = new Ext.tree.TreeNode({
				text: typeTitle, // Stencilset Name
				allowDrag: false,
				allowDrop: false,
				iconCls: 'headerShapeRepImg',
				cls: 'headerShapeRep',
				singleClickExpand: true
			}));

			ORYX.Log.info("stencilSetNode " + stencilSetNode.text);

			this.shapeList.appendChild(stencilSetNode);

			stencilSetNode.render();
			stencilSetNode.expand();
			// Get Stencils from Stencilset
			var stencils = sset.stencils(this.facade.getCanvas().getStencil(),
				this.facade.getRules());
			var treeGroups = new Hash();

			// Sort the stencils according to their position and add them to the repository
			stencils = stencils.sortBy(function (value) { return value.position(); });
			stencils.each((function (value) {

				// Show stencils in no group if there is less than 10 shapes
				//if(stencils.length <= ORYX.CONFIG.MAX_NUM_SHAPES_NO_GROUP) {
				//	this.createStencilTreeNode(stencilSetNode, value);	
				//	return;					
				//}

				// Get the groups name
				var groups = value.groups();

				// For each Group-Entree
				groups.each((function (group) {

					// If there is a new group
					if (!treeGroups[group]) {
						// Create a new group
						treeGroups[group] = new Ext.tree.TreeNode({
							text: group,					// Group-Name
							allowDrag: false,
							allowDrop: false,
							iconCls: 'headerShapeRepImg', // Css-Class for Icon
							cls: 'headerShapeRepChild',  // CSS-Class for Stencil-Group
							singleClickExpand: true
						});

						// Add the Group to the ShapeRepository
						stencilSetNode.appendChild(treeGroups[group]);
						treeGroups[group].render();
					}

					// Create the Stencil-Tree-Node
					this.createStencilTreeNode(treeGroups[group], value);

				}).bind(this));


				// If there is no group
				if (groups.length == 0) {
					// Create the Stencil-Tree-Node
					this.createStencilTreeNode(stencilSetNode, value);
				}

			}).bind(this));
		}).bind(this));
	},

	createStencilTreeNode: function (parentTreeNode, stencil) {

		// Create and add the Stencil to the Group
		var newElement = new Ext.tree.TreeNode({
			text: stencil.title(), 		// Text of the stencil
			icon: stencil.icon(),			// Icon of the stencil
			allowDrag: false,					// Don't use the Drag and Drop of Ext-Tree
			allowDrop: false,
			iconCls: 'ShapeRepEntreeImg', 	// CSS-Class for Icon
			cls: 'ShapeRepEntree'		// CSS-Class for the Tree-Entree
		});

		parentTreeNode.appendChild(newElement);
		newElement.render();

		var ui = newElement.getUI();

		// Set the tooltip
		ui.elNode.setAttributeNS(null, "title", stencil.description());

		// Register the Stencil on Drag and Drop
		Ext.dd.Registry.register(ui.elNode, {
			node: ui.node,
			handles: [ui.elNode, ui.textNode].concat($A(ui.elNode.childNodes)), // Set the Handles
			isHandle: false,
			type: stencil.id(),			// Set Type of stencil 
			namespace: stencil.namespace()		// Set Namespace of stencil
		});

	},

	drop: function (dragZone, target, event) {

		this._lastOverElement = undefined;

		// Hide the highlighting
		this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'shapeRepo.added' });
		this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'shapeRepo.attached' });

		// Check if drop is allowed
		var proxy = dragZone.getProxy()
		if (proxy.dropStatus == proxy.dropNotAllowed) { return }

		// Check if there is a current Parent
		if (!this._currentParent) { return }

		var option = Ext.dd.Registry.getHandle(target.DDM.currentTarget);

		var xy = event.getXY();
		var pos = { x: xy[0], y: xy[1] };

		var a = this.facade.getCanvas().node.getScreenCTM();

		// Correcting the UpperLeft-Offset
		pos.x -= a.e; pos.y -= a.f;
		// Correcting the Zoom-Faktor
		pos.x /= a.a; pos.y /= a.d;
		// Correting the ScrollOffset
		pos.x -= document.documentElement.scrollLeft;
		pos.y -= document.documentElement.scrollTop;
		// Correct position of parent
		var parentAbs = this._currentParent.absoluteXY();
		pos.x -= parentAbs.x;
		pos.y -= parentAbs.y;

		// Set position
		option['position'] = pos

		// Set parent
		if (this._canAttach && this._currentParent instanceof ORYX.Core.Node) {
			option['parent'] = undefined;
		} else {
			option['parent'] = this._currentParent;
		}


		var commandClass = ORYX.Core.Command.extend({
			construct: function (option, currentParent, canAttach, position, facade) {
				this.option = option;
				this.currentParent = currentParent;
				this.canAttach = canAttach;
				this.position = position;
				this.facade = facade;
				this.selection = this.facade.getSelection();
				this.shape;
				this.parent;
			},
			execute: function () {
				if (!this.shape) {
					this.shape = this.facade.createShape(option);
					this.parent = this.shape.parent;
				} else {
					this.parent.add(this.shape);
				}



				if (this.canAttach && this.currentParent instanceof ORYX.Core.Node && this.shape.dockers.length > 0) {

					var docker = this.shape.dockers[0];

					if (this.currentParent.parent instanceof ORYX.Core.Node) {
						this.currentParent.parent.add(docker.parent);
					}

					docker.bounds.centerMoveTo(this.position);
					docker.setDockedShape(this.currentParent);
					//docker.update();	
				}

				//this.currentParent.update();
				//this.shape.update();

				this.facade.setSelection([this.shape]);
				this.facade.getCanvas().update();
				this.facade.updateSelection();

			},
			rollback: function () {
				this.facade.deleteShape(this.shape);

				//this.currentParent.update();

				this.facade.setSelection(this.selection.without(this.shape));
				this.facade.getCanvas().update();
				this.facade.updateSelection();

			}
		});

		var position = this.facade.eventCoordinates(event.browserEvent);

		var command = new commandClass(option, this._currentParent, this._canAttach, position, this.facade);

		this.facade.executeCommands([command]);

		this._currentParent = undefined;
	},

	beforeDragOver: function (dragZone, target, event) {

		var coord = this.facade.eventCoordinates(event.browserEvent);
		var aShapes = this.facade.getCanvas().getAbstractShapesAtPosition(coord);

		if (aShapes.length <= 0) {

			var pr = dragZone.getProxy();
			pr.setStatus(pr.dropNotAllowed);
			pr.sync();

			return false;
		}

		var el = aShapes.last();


		if (aShapes.lenght == 1 && aShapes[0] instanceof ORYX.Core.Canvas) {

			return false;

		} else {
			// check containment rules
			var option = Ext.dd.Registry.getHandle(target.DDM.currentTarget);

			var stencilSet = this.facade.getStencilSets()[option.namespace];

			var stencil = stencilSet.stencil(option.type);

			if (stencil.type() === "node") {

				var parentCandidate = aShapes.reverse().find(function (candidate) {
					return (candidate instanceof ORYX.Core.Canvas
						|| candidate instanceof ORYX.Core.Node
						|| candidate instanceof ORYX.Core.Edge);
				});

				if (parentCandidate !== this._lastOverElement) {

					this._canAttach = undefined;
					this._canContain = undefined;

				}

				if (parentCandidate) {
					//check containment rule					

					if (!(parentCandidate instanceof ORYX.Core.Canvas) && parentCandidate.isPointOverOffset(coord.x, coord.y) && this._canAttach == undefined) {

						this._canAttach = this.facade.getRules().canConnect({
							sourceShape: parentCandidate,
							edgeStencil: stencil,
							targetStencil: stencil
						});

						if (this._canAttach) {
							// Show Highlight
							this.facade.raiseEvent({
								type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
								highlightId: "shapeRepo.attached",
								elements: [parentCandidate],
								style: ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_RECTANGLE,
								color: ORYX.CONFIG.SELECTION_VALID_COLOR
							});

							this.facade.raiseEvent({
								type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
								highlightId: "shapeRepo.added"
							});

							this._canContain = undefined;
						}

					}

					if (!(parentCandidate instanceof ORYX.Core.Canvas) && !parentCandidate.isPointOverOffset(coord.x, coord.y)) {
						this._canAttach = this._canAttach == false ? this._canAttach : undefined;
					}

					if (this._canContain == undefined && !this._canAttach) {

						this._canContain = this.facade.getRules().canContain({
							containingShape: parentCandidate,
							containedStencil: stencil
						});

						// Show Highlight
						this.facade.raiseEvent({
							type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
							highlightId: 'shapeRepo.added',
							elements: [parentCandidate],
							color: this._canContain ? ORYX.CONFIG.SELECTION_VALID_COLOR : ORYX.CONFIG.SELECTION_INVALID_COLOR
						});
						this.facade.raiseEvent({
							type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
							highlightId: "shapeRepo.attached"
						});
					}



					this._currentParent = this._canContain || this._canAttach ? parentCandidate : undefined;
					this._lastOverElement = parentCandidate;
					var pr = dragZone.getProxy();
					pr.setStatus(this._currentParent ? pr.dropAllowed : pr.dropNotAllowed);
					pr.sync();

				}
			} else { //Edge
				this._currentParent = this.facade.getCanvas();
				var pr = dragZone.getProxy();
				pr.setStatus(pr.dropAllowed);
				pr.sync();
			}
		}


		return false
	}
}

ORYX.Plugins.ShapeRepository = Clazz.extend(ORYX.Plugins.ShapeRepository);

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/


if (!ORYX.Plugins) {
	ORYX.Plugins = new Object();
}
/**
 * 节点属性编辑停靠的窗口
 */
ORYX.Plugins.PropertyWindow = {

	facade: undefined,

	construct: function (facade) {
		// Reference to the Editor-Interface
		this.facade = facade;

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_SHOW_PROPERTYWINDOW, this.init.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_LOADED, this.selectDiagram.bind(this));
		this.init();
	},

	init: function () {

		// The parent div-node of the grid
		this.node = ORYX.Editor.graft("http://www.w3.org/1999/xhtml",
			null,
			['div']);

		// If the current property in focus is of type 'Date', the date format
		// is stored here.
		this.currentDateFormat;

		// the properties array
		this.popularProperties = [];
		this.properties = [];

		/* The currently selected shapes whos properties will shown */
		this.shapeSelection = new Hash();
		this.shapeSelection.shapes = new Array();
		this.shapeSelection.commonProperties = new Array();
		this.shapeSelection.commonPropertiesValues = new Hash();

		this.updaterFlag = false;

		// 创建属性grid的列
		this.columnModel = new Ext.grid.ColumnModel([
			{
				//id: 'name',
				header: ORYX.I18N.PropertyWindow.name,
				dataIndex: 'name',
				width: 90,
				sortable: true,
				renderer: this.tooltipRenderer.bind(this)
			}, {
				//id: 'value',
				header: ORYX.I18N.PropertyWindow.value,
				dataIndex: 'value',
				id: 'propertywindow_column_value',
				width: 110,
				editor: new Ext.form.TextField({
					allowBlank: false
				}),
				renderer: this.renderer.bind(this)
			},
			{
				header: "Pop",
				dataIndex: 'popular',
				hidden: true,
				sortable: true
			}
		]);

		// 创建属性grid的数据dataSource
		this.dataSource = new Ext.data.GroupingStore({
			proxy: new Ext.data.MemoryProxy(this.properties),
			reader: new Ext.data.ArrayReader({}, [
				{ name: 'popular' },
				{ name: 'name' },
				{ name: 'value' },
				{ name: 'icons' },
				{ name: 'gridProperties' }
			]),
			sortInfo: { field: 'popular', direction: "ASC" },
			sortData: function (f, direction) {
				direction = direction || 'ASC';
				var st = this.fields.get(f).sortType;
				var fn = function (r1, r2) {
					var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
					var p1 = r1.data['popular'], p2 = r2.data['popular'];
					return p1 && !p2 ? -1 : (!p1 && p2 ? 1 : (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)));
				};
				this.data.sort(direction, fn);
				if (this.snapshot && this.snapshot != this.data) {
					this.snapshot.sort(direction, fn);
				}
			},
			groupField: 'popular'
		});
		this.dataSource.load();

		this.grid = new Ext.grid.EditorGridPanel({
			clicksToEdit: 1,
			stripeRows: true,
			autoExpandColumn: "propertywindow_column_value",
			width: 'auto',
			// the column model
			colModel: this.columnModel,
			enableHdMenu: false,
			view: new Ext.grid.GroupingView({
				forceFit: true,
				groupTextTpl: '{[values.rs.first().data.popular ? ORYX.I18N.PropertyWindow.oftenUsed : ORYX.I18N.PropertyWindow.moreProps]}'
			}),
			// the data store
			store: this.dataSource
		});
		//创建通用修改的form by gk
		this.commonEditPanel = new Ext.Panel({
			title: ""
			, height: 200
			, cls: 'PropertyWindow-CommonEditor'
			, items: [
				new Ext.Panel()
			]
		});

		//创建tab页签组件 by gk
		this.protabs = new Ext.TabPanel({
			ayout: "fit",
			activeTab: 0,
			defaults: {
				autoScroll: true
			},
			items: [
				{
					title: "通用设置",
					layout: "fit",
					items: [this.commonEditPanel]
				}, {
					title: "更多属性",
					layout: "fit",
					items: [this.grid]
				}, {
					title: '帮助',
					html: '<iframe scrolling="auto" frameborder="0" width="100%" height="100%" src="../static/help/workflow/help.htm"' + '> </iframe>'
				}]
		});

		region = this.facade.addToRegion('east', new Ext.Panel({
			width: 220,
			layout: "fit",
			border: false,
			items: [
				this.protabs
			]
		}), ORYX.I18N.PropertyWindow.title)

		// Register on Events
		this.grid.on('beforeedit', this.beforeEdit, this, true);
		this.grid.on('afteredit', this.afterEdit, this, true);
		this.grid.view.on('refresh', this.hideMoreAttrs, this, true);

		//this.grid.on(ORYX.CONFIG.EVENT_KEYDOWN, this.keyDown, this, true);

		// Renderer the Grid
		this.grid.enableColumnMove = false;
		//this.grid.render();

		// Sort as Default the first column
		//this.dataSource.sort('name');

	},

	// Select the Canvas when the editor is ready
	selectDiagram: function () {
		this.shapeSelection.shapes = [this.facade.getCanvas()];

		this.setPropertyWindowTitle();
		this.identifyCommonProperties();
		this.createProperties();
	},

	specialKeyDown: function (field, event) {
		// If there is a TextArea and the Key is an Enter
		if (field instanceof Ext.form.TextArea && event.button == ORYX.CONFIG.KEY_Code_enter) {
			// Abort the Event
			return false
		}
	},
	tooltipRenderer: function (value, p, record) {
		/* Prepare tooltip */
		p.cellAttr = 'title="' + record.data.gridProperties.tooltip + '"';
		return value;
	},

	renderer: function (value, p, record) {

		this.tooltipRenderer(value, p, record);

		if (value instanceof Date) {
			// TODO: Date-Schema is not generic
			value = value.dateFormat(ORYX.I18N.PropertyWindow.dateFormat);
		} else if (String(value).search("<a href='") < 0) {
			// Shows the Value in the Grid in each Line
			value = String(value).gsub("<", "&lt;");
			value = String(value).gsub(">", "&gt;");
			value = String(value).gsub("%", "&#37;");
			value = String(value).gsub("&", "&amp;");

			if (record.data.gridProperties.type == ORYX.CONFIG.TYPE_COLOR) {
				value = "<div class='prop-background-color' style='background-color:" + value + "' />";
			}

			record.data.icons.each(function (each) {
				if (each.name == value) {
					if (each.icon) {
						value = "<img src='" + each.icon + "' /> " + value;
					}
				}
			});
		}

		return value;
	},

	beforeEdit: function (option) {
		var editorGrid = this.dataSource.getAt(option.row).data.gridProperties.editor;
		var editorRenderer = this.dataSource.getAt(option.row).data.gridProperties.renderer;
		if (editorGrid) {
			// Disable KeyDown
			this.facade.disableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
			option.grid.getColumnModel().setEditor(1, editorGrid);
			editorGrid.field.row = option.row;
			// Render the editor to the grid, therefore the editor is also available 
			// for the first and last row
			editorGrid.render(this.grid);
			//option.grid.getColumnModel().setRenderer(1, editorRenderer);
			editorGrid.setSize(option.grid.getColumnModel().getColumnWidth(1), editorGrid.height);
		} else {
			return false;
		}
		var key = this.dataSource.getAt(option.row).data.gridProperties.propId;
		this.oldValues = new Hash();
		this.shapeSelection.shapes.each(function (shape) {
			this.oldValues[shape.getId()] = shape.properties[key];
		}.bind(this));
	},
	/**
	 * 更新数组类型的属性值
	 *如:
	 * key:'dataproperties'  
	 * newValue:'{"dataproperty_id":"ddd","dataproperty_name":"ddd","dataproperty_type":"string","dataproperty_value":"111"}'
	 */
	updateArrayProperty: function (key, newValue, oldValues) {
		var arryProp = eval('(' + this.properties.key + ')');
		var oldArryProp = arryProp;
		if (newValue[0] == oldValues[0]) {
			arryProp.items.each(function (item, index) {
				if (item[0] == newValue[0]) {
					item = newValue;
					return false;
				}
			}.bind(this));
		} else {
			var exists = arryProp.items.each(function (item, index) {
				if (item[0] == newValue[0]) {
					return true
				}
			});
			if (exists) {
				return '已存在属性[' + newValue[0] + ']无法修改属性'
			}
		}
		newValue = JSON.stringify(newValue);
		oldValues = JSON.stringify(oldArryProp);
		update(key, newValue, oldValues);
	},
	/**
	 * 更新选择节点值和刷新界面
	 * 
	 * @param key 属性的key
	 * @param newValue 新的值 
	 * @param oldValues 旧的值
	 */
	update: function (key, newValue, oldValues) {
		var selectedElements = this.shapeSelection.shapes;
		var facade = this.facade;
		// Implement the specific command for property change
		//定义刷新的页面的命令
		var commandClass = ORYX.Core.Command.extend({
			construct: function () {
				this.key = key;
				this.selectedElements = selectedElements;
				this.oldValues = oldValues;
				this.newValue = newValue;
				this.facade = facade;
			},
			execute: function () {
				this.selectedElements.each(function (shape) {
					if (!shape.getStencil().property(this.key).readonly()) {
						shape.setProperty(this.key, this.newValue);
					}
				}.bind(this));
				this.facade.setSelection(this.selectedElements);
				this.facade.getCanvas().update();
				this.facade.updateSelection();
			},
			rollback: function () {
				this.selectedElements.each(function (shape) {
					shape.setProperty(this.key, this.oldValues[shape.getId()]);
				}.bind(this));
				this.facade.setSelection(this.selectedElements);
				this.facade.getCanvas().update();
				this.facade.updateSelection();
			}
		})
		// Instanciated the class
		var command = new commandClass();
		// Execute the command 更新页面选择项的状态
		this.facade.executeCommands([command]);
		// extended by Kerstin (start)
		// 触发EVENT_PROPWINDOW_PROP_CHANGED事件属性修改 更新页面
		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED,
			elements: selectedElements,
			key: key,
			value: newValue
		});
		// extended by Kerstin (end)
	},
	afterEdit: function (option) {
		//Ext1.0: option.grid.getDataSource().commitChanges();
		option.grid.getStore().commitChanges();
		var key = option.record.data.gridProperties.propId;
		var newValue = option.value;
		var oldValues = this.oldValues;
		this.update(key, newValue, oldValues)
	},

	// Changes made in the property window will be shown directly
	editDirectly: function (key, value) {
		this.editDirectlyByShapes(this.shapeSelection.shapes, key, value)
	},
	editDirectlyByShapes: function (shapes, key, value) {
		ORYX.Log.debug('editDirectly key[%0] value[%1]', key, value);
		shapes.each(function (shape) {
			if (!shape.getStencil().property(key).readonly()) {
				shape.setProperty(key, value);
				//shape.update();
			}
		}.bind(this));
		/* Propagate changed properties */
		var selectedElements = shapes;
		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED,
			elements: selectedElements,
			key: key,
			value: value
		});
		this.facade.getCanvas().update();
		this.refreshPropertitesValues();
	},

	// if a field becomes invalid after editing the shape must be restored to the old value
	updateAfterInvalid: function (key) {
		this.shapeSelection.shapes.each(function (shape) {
			if (!shape.getStencil().property(key).readonly()) {
				shape.setProperty(key, this.oldValues[shape.getId()]);
				shape.update();
			}
		}.bind(this));

		this.facade.getCanvas().update();
	},

	// extended by Kerstin (start)	
	dialogClosed: function (data) {
		var row = this.field ? this.field.row : this.row
		this.scope.afterEdit({
			grid: this.scope.grid,
			record: this.scope.grid.getStore().getAt(row),
			//value:this.scope.grid.getStore().getAt(this.row).get("value")
			value: data
		})
		// reopen the text field of the complex list field again
		this.scope.grid.startEditing(row, this.col);
	},
	// extended by Kerstin (end)

	/**
	 * Changes the title of the property window panel according to the selected shapes.
	 */
	setPropertyWindowTitle: function () {
		if (this.shapeSelection.shapes.length == 1) {
			// add the name of the stencil of the selected shape to the title
			region.setTitle(ORYX.I18N.PropertyWindow.title + ' (' + this.shapeSelection.shapes.first().getStencil().title() + ')');
		} else {
			region.setTitle(ORYX.I18N.PropertyWindow.title + ' ('
				+ this.shapeSelection.shapes.length
				+ ' '
				+ ORYX.I18N.PropertyWindow.selected
				+ ')');
		}
	},
	/**
	 * Sets this.shapeSelection.commonPropertiesValues.
	 * If the value for a common property is not equal for each shape the value
	 * is left empty in the property window.
	 */
	setCommonPropertiesValues: function () {
		this.shapeSelection.commonPropertiesValues = new Hash();
		this.shapeSelection.commonProperties.each(function (property) {
			var key = property.prefix() + "-" + property.id();
			var emptyValue = false;
			var firstShape = this.shapeSelection.shapes.first();

			this.shapeSelection.shapes.each(function (shape) {
				if (firstShape.properties[key] != shape.properties[key]) {
					emptyValue = true;
				}
			}.bind(this));

			/* Set property value */
			if (!emptyValue) {
				this.shapeSelection.commonPropertiesValues[key]
					= firstShape.properties[key];
			}
		}.bind(this));
	},

	/**
	 * Returns the set of stencils used by the passed shapes.
	 */
	getStencilSetOfSelection: function () {
		var stencils = new Hash();

		this.shapeSelection.shapes.each(function (shape) {
			stencils[shape.getStencil().id()] = shape.getStencil();
		})
		return stencils;
	},

	/**
	 * Identifies the common Properties of the selected shapes.
	 */
	identifyCommonProperties: function () {
		this.shapeSelection.commonProperties.clear();

		/* 
		 * A common property is a property, that is part of 
		 * the stencil definition of the first and all other stencils.
		 */
		var stencils = this.getStencilSetOfSelection();
		var firstStencil = stencils.values().first();
		var comparingStencils = stencils.values().without(firstStencil);


		if (comparingStencils.length == 0) {
			this.shapeSelection.commonProperties = firstStencil.properties();
		} else {
			var properties = new Hash();

			/* put all properties of on stencil in a Hash */
			firstStencil.properties().each(function (property) {
				properties[property.namespace() + '-' + property.id()
					+ '-' + property.type()] = property;
			});

			/* Calculate intersection of properties. */

			comparingStencils.each(function (stencil) {
				var intersection = new Hash();
				stencil.properties().each(function (property) {
					if (properties[property.namespace() + '-' + property.id()
						+ '-' + property.type()]) {
						intersection[property.namespace() + '-' + property.id()
							+ '-' + property.type()] = property;
					}
				});
				properties = intersection;
			});

			this.shapeSelection.commonProperties = properties.values();
		}
	},

	onSelectionChanged: function (event) {
		/* Event to call afterEdit method */
		this.grid.stopEditing();

		/* Selected shapes */
		this.shapeSelection.shapes = event.elements;

		/* Case: nothing selected */
		if (event.elements.length == 0) {
			this.shapeSelection.shapes = [this.facade.getCanvas()];
		}

		/* subselection available */
		if (event.subSelection) {
			this.shapeSelection.shapes = [event.subSelection];
		}

		this.setPropertyWindowTitle();
		this.identifyCommonProperties();
		this.setCommonPropertiesValues();

		// Create the Properties
		this.createProperties();
	},
	refreshPropertitesValues: function () {
		this.identifyCommonProperties();
		this.setCommonPropertiesValues();
	},
	//创建通用编辑form by gk
	createCommonEditorPanel: function () {
		if (!this.commonEditorBuilder) {
			this.commonEditorBuilder = new Ext.CommonEditorBuilder();
		}
		var id = this.shapeSelection.shapes.first().getStencil().id();
		this.commonEditorForm = this.commonEditorBuilder.buildEditor(id, this);
		//移除通用编辑所有组件
		if (this.commonEditorForm && this.commonEditPanel) {
			this.commonEditPanel.items.each((function (pair, index) {
				this.commonEditPanel.remove(pair, true);
			}).bind(this));
			//this.commonEditPanel.removeAll();
			this.commonEditPanel.add(this.commonEditorForm);
			this.commonEditPanel.doLayout(true);
			if (this.shapeSelection.commonPropertiesValues) {
				var commonEditFields = [], commonEditFields = [];
				for (var key in this.shapeSelection.commonPropertiesValues) {
					commonEditFields.push(key);
				}
				var recordField = Ext.data.Record.create(commonEditFields);
				var record = new recordField(this.shapeSelection.commonPropertiesValues);
				this.commonEditorForm.getForm().loadRecord(record);
			}
			this.commonEditPanel.doLayout(true);
		}
		if (this.shapeSelection.shapes.length != 1) {
			return;
		}
		if (this.shapeSelection.commonProperties) {
			// 循环遍历选择的属性值并填充编辑的from
			this.shapeSelection.commonProperties.each((function (pair, index) {
			}).bind(this));
		}
	},
	/**
	 * Creates the properties for the ExtJS-Grid from the properties of the
	 * selected shapes.
	 * 右侧属性窗口的显示
	 */
	createProperties: function () {
		this.properties = [];
		this.popularProperties = [];

		//创建通用的编辑			
		this.createCommonEditorPanel();

		if (this.shapeSelection.commonProperties) {

			// 循环遍历选择的属性值并填充编辑的grid
			this.shapeSelection.commonProperties.each((function (pair, index) {
				var key = pair.prefix() + "-" + pair.id();
				// Get the property pair
				var name = pair.title();
				var icons = [];
				var attribute = this.shapeSelection.commonPropertiesValues[key];
				//grid的编辑editor
				var editorGrid = undefined;
				var editorRenderer = null;

				if (!pair.readonly()) {
					//根据属性的数据类型来处理
					switch (pair.type()) {
						//字符串
						case ORYX.CONFIG.TYPE_STRING:
							// 如果是多行字符串
							if (pair.wrapLines()) {
								// Set the Editor as TextArea
								var editorTextArea = new Ext.form.TextArea({ alignment: "tl-tl", allowBlank: pair.optional(), msgTarget: 'title', maxLength: pair.length() });
								editorTextArea.on('keyup', function (textArea, event) {
									this.editDirectly(key, textArea.getValue());
								}.bind(this));

								editorGrid = new Ext.Editor(editorTextArea);
							} else {
								// If not, set the Editor as InputField
								var editorInput = new Ext.form.TextField({ allowBlank: pair.optional(), msgTarget: 'title', maxLength: pair.length() });
								editorInput.on('keyup', function (input, event) {
									this.editDirectly(key, input.getValue());
								}.bind(this));

								// reverts the shape if the editor field is invalid
								editorInput.on('blur', function (input) {
									if (!input.isValid(false))
										this.updateAfterInvalid(key);
								}.bind(this));

								editorInput.on("specialkey", function (input, e) {
									if (!input.isValid(false))
										this.updateAfterInvalid(key);
								}.bind(this));

								editorGrid = new Ext.Editor(editorInput);
							}
							break;
						//布尔类型使用checkbox
						case ORYX.CONFIG.TYPE_BOOLEAN:
							// Set the Editor as a CheckBox
							var editorCheckbox = new Ext.form.Checkbox();
							editorCheckbox.on('check', function (c, checked) {
								this.editDirectly(key, checked);
							}.bind(this));

							editorGrid = new Ext.Editor(editorCheckbox);
							break;
						//整数类型使用NumberField
						case ORYX.CONFIG.TYPE_INTEGER:
							// Set as an Editor for Integers
							var numberField = new Ext.form.NumberField({ allowBlank: pair.optional(), allowDecimals: false, msgTarget: 'title', minValue: pair.min(), maxValue: pair.max() });
							numberField.on('keyup', function (input, event) {
								this.editDirectly(key, input.getValue());
							}.bind(this));

							editorGrid = new Ext.Editor(numberField);
							break;
						//浮点类型使用NumberField
						case ORYX.CONFIG.TYPE_FLOAT:
							// Set as an Editor for Float
							var numberField = new Ext.form.NumberField({ allowBlank: pair.optional(), allowDecimals: true, msgTarget: 'title', minValue: pair.min(), maxValue: pair.max() });
							numberField.on('keyup', function (input, event) {
								this.editDirectly(key, input.getValue());
							}.bind(this));

							editorGrid = new Ext.Editor(numberField);

							break;
						//颜色类型使用ColorField
						case ORYX.CONFIG.TYPE_COLOR:
							// Set as a ColorPicker
							// Ext1.0 editorGrid = new gEdit(new form.ColorField({ allowBlank: pair.optional(),  msgTarget:'title' }));
							var editorPicker = new Ext.ux.ColorField({ allowBlank: pair.optional(), msgTarget: 'title', facade: this.facade });
							/*this.facade.registerOnEvent(ORYX.CONFIG.EVENT_COLOR_CHANGE, function(option) {
								this.editDirectly(key, option.value);
							}.bind(this));*/
							editorGrid = new Ext.Editor(editorPicker);
							break;
						//选择框combo
						case ORYX.CONFIG.TYPE_CHOICE:
							var items = pair.items();

							var options = [];
							items.each(function (value) {
								if (value.value() == attribute)
									attribute = value.title();

								options.push([value.icon(), value.title(), value.value()]);

								icons.push({
									name: value.title(),
									icon: value.icon()
								});
							});
							//选择框的数据源store
							var store = new Ext.data.SimpleStore({
								fields: [{ name: 'icon' },
								{ name: 'title' },
								{ name: 'value' }],
								data: options // from states.js
							});

							// 创建combox
							var editorCombo = new Ext.form.ComboBox({
								tpl: '<tpl for="."><div class="x-combo-list-item">{[(values.icon) ? "<img src=\'" + values.icon + "\' />" : ""]} {title}</div></tpl>',
								store: store,
								displayField: 'title',
								valueField: 'value',
								typeAhead: true,
								mode: 'local',
								triggerAction: 'all',
								selectOnFocus: true
							});

							editorCombo.on('select', function (combo, record, index) {
								this.editDirectly(key, combo.getValue());
							}.bind(this))

							editorGrid = new Ext.Editor(editorCombo);

							break;
						//日期
						case ORYX.CONFIG.TYPE_DATE:
							var currFormat = ORYX.I18N.PropertyWindow.dateFormat
							if (!(attribute instanceof Date))
								attribute = Date.parseDate(attribute, currFormat)
							editorGrid = new Ext.Editor(new Ext.form.DateField({ allowBlank: pair.optional(), format: currFormat, msgTarget: 'title' }));
							break;
						//文本
						case ORYX.CONFIG.TYPE_TEXT:

							var cf = new Ext.form.ComplexTextField({
								allowBlank: pair.optional(),
								dataSource: this.dataSource,
								grid: this.grid,
								row: index,
								facade: this.facade
							});
							cf.on('dialogClosed', this.dialogClosed, { scope: this, row: index, col: 1, field: cf });
							editorGrid = new Ext.Editor(cf);
							break;
						//选择对话框
						case ORYX.CONFIG.TYPE_SELECTVALUEDLG:	//选择值的对话框, by gk						
							var cf = new Ext.form.ComplexSelectValueDlgField({
								allowBlank: pair.optional(), dataSource: this.dataSource,
								grid: this.grid,
								row: index,
								facade: this.facade
							});
							cf.on('dialogClosed', this.dialogClosed, { scope: this, row: index, col: 1, field: cf });
							editorGrid = new Ext.Editor(cf);
							break;
						//模型连接
						case ORYX.CONFIG.TYPE_MODEL_LINK:
							var cf = new Ext.form.ComplexModelLinkField({
								allowBlank: pair.optional(),
								dataSource: this.dataSource,
								grid: this.grid,
								row: index,
								facade: this.facade
							});
							cf.on('dialogClosed', this.dialogClosed, { scope: this, row: index, col: 1, field: cf });
							editorGrid = new Ext.Editor(cf);
							break;
						//监听器
						case ORYX.CONFIG.TYPE_LISTENER:
							var cf = new Ext.form.ListenerDefinitionField({
								allowBlank: pair.optional(),
								dataSource: this.dataSource,
								grid: this.grid,
								row: index,
								facade: this.facade
							});
							cf.on('dialogClosed', this.dialogClosed, { scope: this, row: index, col: 1, field: cf });
							editorGrid = new Ext.Editor(cf);
							break;
						// extended by Kerstin (start)
						case ORYX.CONFIG.TYPE_COMPLEX:

							var cf = new Ext.form.ComplexListField({ allowBlank: pair.optional() }, pair.complexItems(), key, this.facade);
							cf.on('dialogClosed', this.dialogClosed, { scope: this, row: index, col: 1, field: cf });
							editorGrid = new Ext.Editor(cf);
							break;
						// extended by Kerstin (end)
						case ORYX.CONFIG.TYPE_MULTIPLECOMPLEX:
							var cf = new Ext.form.MultipleComplexListField({ allowBlank: pair.optional() }, pair.complexItems(), key, this.facade);
							cf.on('dialogClosed', this.dialogClosed, { scope: this, row: index, col: 1, field: cf });
							editorGrid = new Ext.Editor(cf);
							break;
						// extended by Gerardo (Start)
						case "CPNString":
							var editorInput = new Ext.form.TextField(
								{
									allowBlank: pair.optional(),
									msgTarget: 'title',
									maxLength: pair.length(),
									enableKeyEvents: true
								});

							editorInput.on('keyup', function (input, event) {
								this.editDirectly(key, input.getValue());
								//console.log(input.getValue());
								alert("huhu");
							}.bind(this));

							editorGrid = new Ext.Editor(editorInput);
							break;
						// extended by Gerardo (End)
						default:
							var editorInput = new Ext.form.TextField({ allowBlank: pair.optional(), msgTarget: 'title', maxLength: pair.length(), enableKeyEvents: true });
							editorInput.on('keyup', function (input, event) {
								this.editDirectly(key, input.getValue());
							}.bind(this));

							editorGrid = new Ext.Editor(editorInput);
					}
					// Register Event to enable KeyDown
					editorGrid.on('beforehide', this.facade.enableEvent.bind(this, ORYX.CONFIG.EVENT_KEYDOWN));
					editorGrid.on('specialkey', this.specialKeyDown.bind(this));

				} else if (pair.type() === ORYX.CONFIG.TYPE_URL || pair.type() === ORYX.CONFIG.TYPE_DIAGRAM_LINK) {
					attribute = String(attribute).search("http") !== 0 ? ("http://" + attribute) : attribute;
					attribute = "<a href='" + attribute + "' target='_blank'>" + attribute.split("://")[1] + "</a>"
				}

				// Push to the properties-array
				if (pair.visible()) {
					// Popular Properties are those which are set to be popular
					if (pair.popular()) {
						pair.setPopular();
					}

					if (pair.popular()) {
						this.popularProperties.push([pair.popular(), name, attribute, icons, {
							editor: editorGrid,
							propId: key,
							type: pair.type(),
							tooltip: pair.description(),
							renderer: editorRenderer
						}]);
					}
					else {
						this.properties.push([pair.popular(), name, attribute, icons, {
							editor: editorGrid,
							propId: key,
							type: pair.type(),
							tooltip: pair.description(),
							renderer: editorRenderer
						}]);
					}
				}

			}).bind(this));
		}

		this.setProperties();
	},

	hideMoreAttrs: function (panel) {
		// TODO: Implement the case that the canvas has no attributes
		if (this.properties.length <= 0) { return }

		// collapse the "more attr" group
		this.grid.view.toggleGroup(this.grid.view.getGroupId(this.properties[0][0]), false);

		// prevent the more attributes pane from closing after a attribute has been edited
		this.grid.view.un("refresh", this.hideMoreAttrs, this);
	},

	setProperties: function () {
		var props = this.popularProperties.concat(this.properties);

		this.dataSource.loadData(props);
	}
}
ORYX.Plugins.PropertyWindow = Clazz.extend(ORYX.Plugins.PropertyWindow);


/**
 * Editor for complex type
 * 
 * When starting to edit the editor, it creates a new dialog where new attributes
 * can be specified which generates json out of this and put this 
 * back to the input field.
 * 
 * This is implemented from Kerstin Pfitzner
 * 
 * @param {Object} config
 * @param {Object} items
 * @param {Object} key
 * @param {Object} facade
 */


Ext.form.ComplexListField = function (config, items, key, facade) {
	Ext.form.ComplexListField.superclass.constructor.call(this, config);
	this.items = items;
	this.key = key;
	this.facade = facade;
};

/**
 * This is a special trigger field used for complex properties.
 * The trigger field opens a dialog that shows a list of properties.
 * The entered values will be stored as trigger field value in the JSON format.
 */
Ext.extend(Ext.form.ComplexListField, Ext.form.TriggerField, {
	/**
     * @cfg {String} triggerClass
     * An additional CSS class used to style the trigger button.  The trigger will always get the
     * class 'x-form-trigger' and triggerClass will be <b>appended</b> if specified.
     */
	triggerClass: 'x-form-complex-trigger',
	readOnly: true,
	emptyText: ORYX.I18N.PropertyWindow.clickIcon,

	/**
	 * Builds the JSON value from the data source of the grid in the dialog.
	 */
	buildValue: function () {
		var ds = this.grid.getStore();
		ds.commitChanges();

		if (ds.getCount() == 0) {
			return "";
		}

		var jsonString = "[";
		for (var i = 0; i < ds.getCount(); i++) {
			var data = ds.getAt(i);
			jsonString += "{";
			for (var j = 0; j < this.items.length; j++) {
				var key = this.items[j].id();
				jsonString += key + ':' + ("" + data.get(key)).toJSON();
				if (j < (this.items.length - 1)) {
					jsonString += ", ";
				}
			}
			jsonString += "}";
			if (i < (ds.getCount() - 1)) {
				jsonString += ", ";
			}
		}
		jsonString += "]";

		jsonString = "{'totalCount':" + ds.getCount().toJSON() +
			", 'items':" + jsonString + "}";
		return Object.toJSON(jsonString.evalJSON());
	},

	/**
	 * Returns the field key.
	 */
	getFieldKey: function () {
		return this.key;
	},

	/**
	 * Returns the actual value of the trigger field.
	 * If the table does not contain any values the empty
	 * string will be returned.
	 */
	getValue: function () {
		// return actual value if grid is active
		if (this.grid) {
			return this.buildValue();
		} else if (this.data == undefined) {
			return "";
		} else {
			return this.data;
		}
	},

	/**
	 * Sets the value of the trigger field.
	 * In this case this sets the data that will be shown in
	 * the grid of the dialog.
	 * 
	 * @param {Object} value The value to be set (JSON format or empty string)
	 */
	setValue: function (value) {
		if (value.length > 0 && value.indexOf('<') == -1) {
			// set only if this.data not set yet
			// only to initialize the grid
			if (this.data == undefined) {
				this.data = value;
			}
		}
	},

	/**
	 * Returns false. In this way key events will not be propagated
	 * to other elements.
	 * 
	 * @param {Object} event The keydown event.
	 */
	keydownHandler: function (event) {
		return false;
	},

	/**
	 * The listeners of the dialog. 
	 * 
	 * If the dialog is hidded, a dialogClosed event will be fired.
	 * This has to be used by the parent element of the trigger field
	 * to reenable the trigger field (focus gets lost when entering values
	 * in the dialog).
	 */
	dialogListeners: {
		show: function () { // retain focus styling
			this.onFocus();
			this.facade.registerOnEvent(ORYX.CONFIG.EVENT_KEYDOWN, this.keydownHandler.bind(this));
			this.facade.disableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
			return;
		},
		hide: function () {

			var dl = this.dialogListeners;
			this.dialog.un("show", dl.show, this);
			this.dialog.un("hide", dl.hide, this);

			this.dialog.destroy(true);
			this.grid.destroy(true);
			delete this.grid;
			delete this.dialog;

			this.facade.unregisterOnEvent(ORYX.CONFIG.EVENT_KEYDOWN, this.keydownHandler.bind(this));
			this.facade.enableEvent(ORYX.CONFIG.EVENT_KEYDOWN);

			// store data and notify parent about the closed dialog
			// parent has to handel this event and start editing the text field again
			this.fireEvent('dialogClosed', this.data);

			Ext.form.ComplexListField.superclass.setValue.call(this, this.data);
		}
	},

	/**
	 * Builds up the initial values of the grid.
	 * 
	 * @param {Object} recordType The record type of the grid.
	 * @param {Object} items      The initial items of the grid (columns)
	 */
	buildInitial: function (recordType, items) {
		var initial = new Hash();

		for (var i = 0; i < items.length; i++) {
			var id = items[i].id();
			initial[id] = items[i].value();
		}

		var RecordTemplate = Ext.data.Record.create(recordType);
		return new RecordTemplate(initial);
	},

	/**
	 * Builds up the column model of the grid. The parent element of the
	 * grid.
	 * 
	 * Sets up the editors for the grid columns depending on the 
	 * type of the items.
	 * 
	 * @param {Object} parent The 
	 */
	buildColumnModel: function (parent) {
		var cols = [];
		for (var i = 0; i < this.items.length; i++) {
			var id = this.items[i].id();
			var header = this.items[i].name();
			var width = this.items[i].width();
			var type = this.items[i].type();
			var itemsurl = this.items[i]._jsonItem.itemsurl;
			var editor;

			if (type == ORYX.CONFIG.TYPE_STRING) {
				editor = new Ext.form.TextField({ allowBlank: this.items[i].optional(), width: width });
			} else if (type == ORYX.CONFIG.TYPE_CHOICE) {
				var items = this.items[i].items();
				var datas = [];
				items.each(function (value) {
					datas.push([value.title(), value.value()]);
				});

				var cmbstore = new Ext.data.SimpleStore({
					fields: [
						{ name: 'title' },
						{ name: 'value' }],
					data: datas
				});
				editor = new Ext.form.ComboBox(
					{ mode: 'local', typeAhead: true, triggerAction: 'all', displayField: "title", valueField: "value", store: cmbstore, lazyRender: true, msgTarget: 'title', width: width });
				var rendererfuc = function (value, cellmeta, record) {
					var store = this.store;
					if (store != undefined) {
						index = store.find(this.valueField, value);
						if (index != -1) {
							rs = store.getAt(index).data;
							return rs.title;
						}
					}
				}
			} else if (type == ORYX.CONFIG.TYPE_BOOLEAN) {
				editor = new Ext.form.Checkbox({ width: width });
			} else if (type == ORYX.CONFIG.TYPE_COMPLEX) {
				continue;
			}

			cols.push({
				id: id,
				header: header,
				dataIndex: id,
				resizable: true,
				editor: editor,
				width: width,
				renderer: rendererfuc != undefined ? rendererfuc.bind(editor) : null
			});

		}
		return new Ext.grid.ColumnModel(cols);
	},

	/**
	 * After a cell was edited the changes will be commited.
	 * 
	 * @param {Object} option The option that was edited.
	 */
	afterEdit: function (option) {
		option.grid.getStore().commitChanges();
	},

	/**
	 * Before a cell is edited it has to be checked if this 
	 * cell is disabled by another cell value. If so, the cell editor will
	 * be disabled.
	 * 
	 * @param {Object} option The option to be edited.
	 */
	beforeEdit: function (option) {

		var state = this.grid.getView().getScrollState();

		var col = option.column;
		var row = option.row;

		var editId = this.grid.getColumnModel().config[col].id;
		// check if there is an item in the row, that disables this cell
		for (var i = 0; i < this.items.length; i++) {
			// check each item that defines a "disable" property
			var item = this.items[i];
			var disables = item.disable();
			if (disables != undefined) {

				// check if the value of the column of this item in this row is equal to a disabling value
				var value = this.grid.getStore().getAt(row).get(item.id());
				for (var j = 0; j < disables.length; j++) {
					var disable = disables[j];
					if (disable.value == value) {

						for (var k = 0; k < disable.items.length; k++) {
							// check if this value disables the cell to select 
							// (id is equals to the id of the column to edit)
							var disItem = disable.items[k];
							if (disItem == editId) {
								this.grid.getColumnModel().getCellEditor(col, row).disable();
								return;
							}
						}
					}
				}
			}
		}
		this.grid.getColumnModel().getCellEditor(col, row).enable();
		//this.grid.getView().restoreScroll(state);
	},

	onCellClick: function () {
		alert()
	},

    /**
     * If the trigger was clicked a dialog has to be opened
     * to enter the values for the complex property.
     */
	onTriggerClick: function () {
		if (this.disabled) {
			return;
		}

		//if(!this.dialog) { 

		var dialogWidth = 0;
		var recordType = [];

		for (var i = 0; i < this.items.length; i++) {
			var id = this.items[i].id();
			var width = this.items[i].width();
			var type = this.items[i].type();

			if (type == ORYX.CONFIG.TYPE_CHOICE || type == ORYX.CONFIG.TYPE_COMPLEX) {
				type = ORYX.CONFIG.TYPE_STRING;
			}

			dialogWidth += width;
			recordType[i] = { name: id, type: type };
		}

		if (dialogWidth > 800) {
			dialogWidth = 800;
		}
		dialogWidth += 22;

		var data = this.data;

		if (data == "") {
			// empty string can not be parsed
			data = "{}";
		}

		var ds = new Ext.data.Store({
			proxy: new Ext.data.MemoryProxy(eval("(" + data + ")")),
			reader: new Ext.data.JsonReader({
				root: 'items',
				totalProperty: 'totalCount'
			}, recordType)
		});
		ds.load();


		var cm = this.buildColumnModel();



		//var gridHead = this.grid.getView().getHeaderPanel(true);
		var toolbar = new Ext.Toolbar(
			[{
				text: ORYX.I18N.PropertyWindow.add,
				icon: getContextPath() + '/static/workflow/editor/images/add.png',
				iconCls: "x-dummy",
				handler: function () {
					var ds = this.grid.getStore();
					var index = ds.getCount();
					this.grid.stopEditing();
					var p = this.buildInitial(recordType, this.items);
					ds.insert(index, p);
					ds.commitChanges();
					this.grid.startEditing(index, 0);
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.rem,
				icon: getContextPath() + '/static/workflow/editor/images/delete.png',
				iconCls: "x-dummy",
				handler: function () {
					var ds = this.grid.getStore();
					var selection = this.grid.getSelectionModel().getSelectedCell();
					if (selection == undefined) {
						return;
					}
					this.grid.getSelectionModel().clearSelections();
					this.grid.stopEditing();
					var record = ds.getAt(selection[0]);
					ds.remove(record);
					ds.commitChanges();
				}.bind(this)
			}]);

		this.grid = new Ext.grid.EditorGridPanel({
			store: ds,
			cm: cm,
			stripeRows: true,
			clicksToEdit: 1,
			autoScroll: true,
			stateId: "x-editor-complex-grid",
			anchor: "100% 100%",
			enableHdMenu: false, // Disable header menu
			selModel: new Ext.grid.CellSelectionModel(),

			tbar: toolbar

		});

		// Basic Dialog
		this.dialog = new Ext.Window({
			autoCreate: true,
			layout: "anchor",
			title: ORYX.I18N.PropertyWindow.complex,
			height: 350,
			width: dialogWidth,
			modal: true,
			collapsible: false,
			fixedcenter: true,
			shadow: true,
			proxyDrag: true,
			keys: [{
				key: 27,
				fn: function () {
					this.dialog.hide
				}.bind(this)
			}],
			items: [this.grid],
			bodyStyle: "background-color:#FFFFFF",
			buttons: [{
				text: ORYX.I18N.PropertyWindow.ok,
				handler: function () {
					this.grid.stopEditing();
					// store dialog input
					this.data = this.buildValue();
					this.dialog.hide()
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.cancel,
				handler: function () {
					this.dialog.hide()
				}.bind(this)
			}]
		});

		this.dialog.on(Ext.apply({}, this.dialogListeners, {
			scope: this
		}));

		this.dialog.show();


		this.grid.on('beforeedit', this.beforeEdit, this, true);
		this.grid.on('afteredit', this.afterEdit, this, true);

		this.grid.render();

		/*} else {
			this.dialog.show();		
		}*/

	}
});

Ext.form.MultipleComplexListField = function (config, items, key, facade) {
	Ext.form.MultipleComplexListField.superclass.constructor.call(this, config);
	this.items = items;
	this.key = key;
	this.facade = facade;
};

Ext.extend(Ext.form.MultipleComplexListField, Ext.form.TriggerField, {
	/**
     * @cfg {String} triggerClass
     * An additional CSS class used to style the trigger button.  The trigger will always get the
     * class 'x-form-trigger' and triggerClass will be <b>appended</b> if specified.
     */
	triggerClass: 'x-form-complex-trigger',
	readOnly: true,
	emptyText: ORYX.I18N.PropertyWindow.clickIcon,

	/**
	 * Builds the JSON value from the data source of the grid in the dialog.
	 * 构建需要更新的json值
	 */
	buildValue: function () {
		var ds = this.grid.getStore();
		ds.commitChanges();

		if (ds.getCount() == 0) {
			return "";
		}

		var jsonString = "[";
		for (var i = 0; i < ds.getCount(); i++) {
			var data = ds.getAt(i);
			jsonString += "{";
			for (var j = 0; j < this.items.length; j++) {
				var key = this.items[j].id();
				jsonString += key + ':' + ("" + data.get(key)).toJSON();
				if (j < (this.items.length - 1)) {
					jsonString += ", ";
				}
			}
			jsonString += "}";
			if (i < (ds.getCount() - 1)) {
				jsonString += ", ";
			}
		}
		jsonString += "]";

		jsonString = "{'totalCount':" + ds.getCount().toJSON() +
			", 'items':" + jsonString + "}";
		return Object.toJSON(jsonString.evalJSON());
	},

	/**
	 * Returns the field key.
	 */
	getFieldKey: function () {
		return this.key;
	},

	/**
	 * Returns the actual value of the trigger field.
	 * If the table does not contain any values the empty
	 * string will be returned.
	 */
	getValue: function () {
		// return actual value if grid is active
		if (this.grid) {
			return this.buildValue();
		} else if (this.data == undefined) {
			return "";
		} else {
			return this.data;
		}
	},

	/**
	 * Sets the value of the trigger field.
	 * In this case this sets the data that will be shown in
	 * the grid of the dialog.
	 * 
	 * @param {Object} value The value to be set (JSON format or empty string)
	 */
	setValue: function (value) {
		if (value.length > 0 && value.indexOf('<') == -1) {
			// set only if this.data not set yet
			// only to initialize the grid
			if (this.data == undefined) {
				this.data = value;
			}
		}
	},

	/**
	 * Returns false. In this way key events will not be propagated
	 * to other elements.
	 * 
	 * @param {Object} event The keydown event.
	 */
	keydownHandler: function (event) {
		return false;
	},

	/**
	 * The listeners of the dialog. 
	 * 
	 * If the dialog is hidded, a dialogClosed event will be fired.
	 * This has to be used by the parent element of the trigger field
	 * to reenable the trigger field (focus gets lost when entering values
	 * in the dialog).
	 */
	dialogListeners: {
		show: function () { // retain focus styling
			this.onFocus();
			this.facade.registerOnEvent(ORYX.CONFIG.EVENT_KEYDOWN, this.keydownHandler.bind(this));
			this.facade.disableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
			return;
		},
		hide: function () {

			var dl = this.dialogListeners;
			this.dialog.un("show", dl.show, this);
			this.dialog.un("hide", dl.hide, this);

			this.dialog.destroy(true);
			this.grid.destroy(true);
			delete this.grid;
			delete this.dialog;

			this.facade.unregisterOnEvent(ORYX.CONFIG.EVENT_KEYDOWN, this.keydownHandler.bind(this));
			this.facade.enableEvent(ORYX.CONFIG.EVENT_KEYDOWN);

			// store data and notify parent about the closed dialog
			// parent has to handel this event and start editing the text field again
			this.fireEvent('dialogClosed', this.data);

			Ext.form.ComplexListField.superclass.setValue.call(this, this.data);
		}
	},

	/**
	 * Builds up the initial values of the grid.
	 * 
	 * @param {Object} recordType The record type of the grid.
	 * @param {Object} items      The initial items of the grid (columns)
	 */
	buildInitial: function (recordType, items) {
		var initial = new Hash();

		for (var i = 0; i < items.length; i++) {
			var id = items[i].id();
			initial[id] = items[i].value();
		}

		var RecordTemplate = Ext.data.Record.create(recordType);
		return new RecordTemplate(initial);
	},
	getContextPath: function () {

		var pathName = document.location.pathname;
		var index = pathName.substr(1).indexOf("/");
		var result = pathName.substr(0, index + 1);
		return result;
	},
	/**
	 * Builds up the column model of the grid. The parent element of the
	 * grid.
	 * 
	 * Sets up the editors for the grid columns depending on the 
	 * type of the items.
	 * 创建grid列模型
	 * @param {Object} parent The 
	 */
	buildColumnModel: function (parent) {
		var cols = [];
		for (var i = 0; i < this.items.length; i++) {
			var id = this.items[i].id();
			var header = this.items[i].name();
			var width = this.items[i].width();
			var type = this.items[i].type();
			var hidden = this.items[i]._jsonItem.hidden;
			var itemsurl = this.items[i]._jsonItem.itemsurl;
			var editor;

			if (type == ORYX.CONFIG.TYPE_STRING) {
				editor = new Ext.form.TextField({ allowBlank: this.items[i].optional(), width: width });
			} else if (type == ORYX.CONFIG.TYPE_CHOICE) {
				var items = this.items[i].items();
				var select = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", parent, ['select', { style: 'display:none' }]);
				var datas = [];
				items.each(function (value) {
					datas.push([value.title(), value.value()]);
				});
				var fields = [
					{ name: 'title' },
					{ name: 'value' },
					{ name: 'help' },
				];
				if (itemsurl != undefined) {
					var cmbstore = Helper.getJsonStore(
						this.getContextPath() + itemsurl,
						fields,
						'obj'
					)
					var cmboptions = {
						mode: 'local', typeAhead: true, triggerAction: 'all', displayField: "title", valueField: "value", store: cmbstore, lazyRender: true, msgTarget: 'title', width: width,
						listeners: {
							'select': function (combo, record, index) {

								var tabpage = Ext.getDom('oryx_zlfw_cl_help');
								tabpage.innerHTML = '<h3>' + record.data.help + '</h3>';
							}
						}
					};
				} else {
					var cmbstore = new Ext.data.SimpleStore({
						fields: fields,
						data: datas
					});
					var cmboptions = { mode: 'local', typeAhead: true, triggerAction: 'all', displayField: "title", valueField: "value", store: cmbstore, lazyRender: true, msgTarget: 'title', width: width };
				}
				editor = new Ext.form.ComboBox(cmboptions);

				var rendererfuc = function (value, cellmeta, record) {
					var store = this.store;
					if (store != undefined) {
						index = store.find(this.valueField, value);
						if (index != -1) {
							rs = store.getAt(index).data;
							return rs.title;
						}
					}
					return value;
				}
			} else if (type == ORYX.CONFIG.TYPE_BOOLEAN) {
				editor = new Ext.form.Checkbox({ width: width });
			} else if (type == ORYX.CONFIG.TYPE_COMPLEX) {
				continue;
			}
			cols.push({
				id: id,
				header: header,
				dataIndex: id,
				resizable: true,
				editor: editor,
				hidden: hidden,
				width: width,
				renderer: rendererfuc != undefined ? rendererfuc.bind(editor) : null
			});

		}
		return new Ext.grid.ColumnModel(cols);
	},

	buildSecondColumnModel: function (parent) {
		var cols = [];
		for (var i = 0; i < this.items.length; i++) {

			var parentType = this.items[i].type();

			if (parentType != ORYX.CONFIG.TYPE_COMPLEX) {
				continue;
			}

			var complexItems = this.items[i].complexItems();

			for (var j = 0; j < complexItems.length; j++) {
				var id = complexItems[j].id();
				var header = complexItems[j].name();
				var type = complexItems[j].type();
				var width = complexItems[j].width();
				var hidden = complexItems[j]._jsonItem.hidden;
				var editor;

				if (type == ORYX.CONFIG.TYPE_STRING) {
					editor = new Ext.form.TextArea({ allowBlank: complexItems[j].optional(), width: width, maxLength: 2000, maxLengthText: "输入内容太长了" });
				} else if (type == ORYX.CONFIG.TYPE_CHOICE) {
					var items = complexItems[j].items();
					var select = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", parent, ['select', { style: 'display:none' }]);
					var datas = [];
					items.each(function (value) {
						datas.push([value.title(), value.value()]);
					});
					var cmbstore = new Ext.data.SimpleStore({
						fields: [
							{ name: 'title' },
							{ name: 'value' }],
						data: datas
					});
					editor = new Ext.form.ComboBox(
						{ mode: 'local', typeAhead: true, triggerAction: 'all', displayField: "title", valueField: "value", store: cmbstore, transform: select, lazyRender: true, msgTarget: 'title', width: width });
					var rendererfuc = function (value, cellmeta, record) {
						var store = this.store;
						if (store != undefined) {
							index = store.find(this.valueField, value);
							if (index != -1) {
								rs = store.getAt(index).data;
								return rs.title;
							}
						}
					}
				} else if (type == ORYX.CONFIG.TYPE_SELECTVALUEDLG) {

				} else if (type == ORYX.CONFIG.TYPE_BOOLEAN) {
					editor = new Ext.form.Checkbox({ width: width });
				} else if (type == ORYX.CONFIG.TYPE_COMPLEX) {
					continue;
				}

				cols.push({
					id: id,
					header: header,
					dataIndex: id,
					resizable: true,
					editor: editor,
					width: width,
					hidden: hidden,
					renderer: rendererfuc != undefined ? rendererfuc.bind(editor) : null
				});
			}
		}
		return new Ext.grid.ColumnModel(cols);
	},

	/**
	 * After a cell was edited the changes will be commited.
	 * 
	 * @param {Object} option The option that was edited.
	 */
	afterEdit: function (option) {
		option.grid.getStore().commitChanges();
	},

	afterEditSecondGrid: function (option) {
		this.secondGrid.getStore().commitChanges();
		var selectedCell = this.grid.getSelectionModel().getSelectedCell();
		if (selectedCell.length == 2) {
			var row = selectedCell[0];

			var jsonString = "[";
			for (var i = 0; i < this.secondGrid.getStore().getCount(); i++) {
				var data = this.secondGrid.getStore().getAt(i);
				jsonString += "{";
				for (var j = 0; j < this.items.length; j++) {

					var parentType = this.items[j].type();

					if (parentType != ORYX.CONFIG.TYPE_COMPLEX) {
						continue;
					}

					var complexItems = this.items[j].complexItems();

					for (var k = 0; k < complexItems.length; k++) {

						var key = complexItems[k].id();

						jsonString += key + ':' + ("" + data.get(key)).toJSON();
						if (k < (complexItems.length - 1)) {
							jsonString += ", ";
						}
					}
				}
				jsonString += "}";
				if (i < (this.secondGrid.getStore().getCount() - 1)) {
					jsonString += ", ";
				}
			}
			jsonString += "]";

			jsonString = "{'totalCount':" + this.secondGrid.getStore().getCount().toJSON() +
				", 'items':" + jsonString + "}";

			var activeRecord = this.grid.getStore().getAt(row);
			activeRecord.set(this.complexFieldId, Object.toJSON(jsonString.evalJSON()));
		}
	},

	/**
	 * Before a cell is edited it has to be checked if this 
	 * cell is disabled by another cell value. If so, the cell editor will
	 * be disabled.
	 * 
	 * @param {Object} option The option to be edited.
	 */
	beforeEdit: function (option) {

		var state = this.grid.getView().getScrollState();

		var col = option.column;
		var row = option.row;

		var editId = this.grid.getColumnModel().config[col].id;
		// check if there is an item in the row, that disables this cell
		for (var i = 0; i < this.items.length; i++) {

			if (this.items[i].type() == ORYX.CONFIG.TYPE_COMPLEX) {
				continue;
			}

			// check each item that defines a "disable" property
			var item = this.items[i];
			var disables = item.disable();
			if (disables != undefined) {

				// check if the value of the column of this item in this row is equal to a disabling value
				var value = this.grid.getStore().getAt(row).get(item.id());
				for (var j = 0; j < disables.length; j++) {
					var disable = disables[j];
					if (disable.value == value) {

						for (var k = 0; k < disable.items.length; k++) {
							// check if this value disables the cell to select 
							// (id is equals to the id of the column to edit)
							var disItem = disable.items[k];
							if (disItem == editId) {
								this.grid.getColumnModel().getCellEditor(col, row).disable();
								return;
							}
						}
					}
				}
			}
		}
		this.grid.getColumnModel().getCellEditor(col, row).enable();
		//this.grid.getView().restoreScroll(state);
	},

    /**
     * If the trigger was clicked a dialog has to be opened
     * to enter the values for the complex property.
     */
	onTriggerClick: function () {
		if (this.disabled) {
			return;
		}

		//if(!this.dialog) { 

		var dialogWidth = 0;
		var recordType = [];
		var secondRecordType = [];

		this.complexFieldId;
		var complexItems;

		for (var i = 0; i < this.items.length; i++) {

			var id = this.items[i].id();
			var width = this.items[i].width();
			var type = this.items[i].type();

			if (type == ORYX.CONFIG.TYPE_CHOICE) {
				type = ORYX.CONFIG.TYPE_STRING;
			}

			if (type == ORYX.CONFIG.TYPE_COMPLEX) {
				this.complexFieldId = id;
				type = ORYX.CONFIG.TYPE_STRING;

				complexItems = this.items[i].complexItems();

				for (var j = 0; j < complexItems.length; j++) {
					var secondId = complexItems[j].id();
					var secondWidth = complexItems[j].width();
					var secondType = complexItems[j].type();

					if (secondType == ORYX.CONFIG.TYPE_CHOICE) {
						secondType = ORYX.CONFIG.TYPE_STRING;
					}

					secondRecordType[j] = { name: secondId, type: secondType };
				}

			} else {
				dialogWidth += width;
			}
			recordType[i] = { name: id, type: type };
		}

		if (dialogWidth > 800) {
			dialogWidth = 800;
		}
		dialogWidth += 22;

		var data = this.data;
		if (data == "") {
			// empty string can not be parsed
			data = "{}";
		}

		var ds = new Ext.data.Store({
			proxy: new Ext.data.MemoryProxy(eval("(" + data + ")")),
			reader: new Ext.data.JsonReader({
				root: 'items',
				totalProperty: 'totalCount'
			}, recordType)
		});
		ds.load();

		var secondDs = new Ext.data.Store();

		var cm = this.buildColumnModel();

		var secondCm = this.buildSecondColumnModel();

		//var gridHead = this.grid.getView().getHeaderPanel(true);
		var toolbar = new Ext.Toolbar(
			[{
				text: ORYX.I18N.PropertyWindow.add,
				icon: getContextPath() + '/static/workflow/editor/images/add.png',
				iconCls: "x-dummy",
				handler: function () {
					var ds = this.grid.getStore();
					var index = ds.getCount();
					this.grid.stopEditing();
					var p = this.buildInitial(recordType, this.items);
					ds.insert(index, p);
					ds.commitChanges();
					this.grid.startEditing(index, 0);
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.rem,
				icon: getContextPath() + '/static/workflow/editor/images/delete.png',
				iconCls: "x-dummy",
				handler: function () {
					var ds = this.grid.getStore();
					var selection = this.grid.getSelectionModel().getSelectedCell();
					if (selection == undefined) {
						return;
					}
					this.grid.getSelectionModel().clearSelections();
					this.grid.stopEditing();
					var record = ds.getAt(selection[0]);
					ds.remove(record);
					ds.commitChanges();
				}.bind(this)
			}]);

		this.grid = new Ext.grid.EditorGridPanel({
			store: ds,
			cm: cm,
			stripeRows: true,
			clicksToEdit: 1,
			autoScroll: true,
			stateId: "x-editor-complex-grid",
			height: 280,
			enableHdMenu: false, // Disable header menu
			selModel: new Ext.grid.CellSelectionModel({
				listeners: {
					scope: this,
					cellselect: function (sm, row, col) {
						secondDs.removeAll(true);
						var masterRecord = ds.getAt(row);
						if (masterRecord.get('' + this.complexFieldId) != undefined && masterRecord.get('' + this.complexFieldId) != 'undefined') {

							var newDs = new Ext.data.Store({
								proxy: new Ext.data.MemoryProxy(eval("(" + masterRecord.get('' + this.complexFieldId) + ")")),
								reader: new Ext.data.JsonReader({
									root: 'items',
									totalProperty: 'totalCount'
								}, secondRecordType)
							});
							newDs.load();

							secondDs.removeAll(true);
							for (var i = 0; i < newDs.getCount(); i++) {
								secondDs.add(newDs.getAt(i));
							}
						}
						secondDs.commitChanges();
					}
				}
			}),

			tbar: toolbar

		});

		var secondToolbar = new Ext.Toolbar(
			[{
				text: ORYX.I18N.PropertyWindow.add,
				icon: getContextPath() + '/static/workflow/editor/images/add.png',
				iconCls: "x-dummy",
				handler: function () {
					var ds = this.secondGrid.getStore();
					var index = ds.getCount();
					this.secondGrid.stopEditing();
					var p = this.buildInitial(secondRecordType, complexItems);
					ds.insert(index, p);
					ds.commitChanges();
					this.secondGrid.startEditing(index, 0);
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.delete,
				icon: getContextPath() + '/static/workflow/editor/images/delete.png',
				iconCls: "x-dummy",
				handler: function () {
					var ds = this.secondGrid.getStore();
					var selection = this.secondGrid.getSelectionModel().getSelectedCell();
					if (selection == undefined) {
						return;
					}
					this.secondGrid.getSelectionModel().clearSelections();
					this.secondGrid.stopEditing();
					var record = ds.getAt(selection[0]);
					ds.remove(record);
					ds.commitChanges();
					this.afterEditSecondGrid();
				}.bind(this)
			}]);

		this.secondGrid = new Ext.grid.EditorGridPanel({
			store: secondDs,
			cm: secondCm,
			stripeRows: true,
			clicksToEdit: 1,
			autoScroll: true,
			stateId: "x-editor-complex-grid",
			height: 280,
			enableHdMenu: false, // Disable header menu
			selModel: new Ext.grid.CellSelectionModel(),
			tbar: secondToolbar

		});

		this.tabs = new Ext.TabPanel({

			ayout: "fit",
			activeTab: 0,
			defaults: {
				autoScroll: true
			},
			items: [{
				id: "oryx_zlfw_cl_help",
				title: '帮助',
				html: "帮助"
			}]
		});
		var border = new Ext.Panel({
			layout: 'border',
			items: [
				{
					region: 'east',
					width: 200,
					layout: "fit",
					items: [
						this.tabs
					]
				}, {
					region: 'center',
					layout: "fit",
					height: 280,
					items: [
						new Ext.Panel({
							layout: 'border',
							items: [
								{
									region: 'center',
									layout: "fit",
									height: 100,
									items: [this.grid]
								}, {
									region: 'south',
									layout: "fit",
									height: 300,
									items: [this.secondGrid]
								}
							]
						})
					]
				}
			]

		});
		// Basic Dialog
		this.dialog = new Ext.Window({
			autoCreate: true,
			layout: "fit",
			title: ORYX.I18N.PropertyWindow.complex,
			height: 600,
			width: dialogWidth,
			modal: true,
			collapsible: false,
			fixedcenter: true,
			shadow: true,
			proxyDrag: true,
			keys: [{
				key: 27,
				fn: function () {
					this.dialog.hide
				}.bind(this)
			}],
			items: [
				border
			],
			bodyStyle: "background-color:#FFFFFF",
			buttons: [{
				text: ORYX.I18N.PropertyWindow.ok,
				handler: function () {
					this.grid.stopEditing();
					// store dialog input
					this.data = this.buildValue();
					this.dialog.hide()
				}.bind(this)
			}]
		});

		this.dialog.on(Ext.apply({}, this.dialogListeners, {
			scope: this
		}));

		this.dialog.show();


		this.grid.on('beforeedit', this.beforeEdit, this, true);
		this.grid.on('afteredit', this.afterEdit, this, true);

		this.secondGrid.on('beforeedit', this.beforeEdit, this, true);
		this.secondGrid.on('afteredit', this.afterEditSecondGrid, this, true);

		this.grid.render();
		this.secondGrid.render();

		if (ds.getCount() > 0) {
			this.grid.getSelectionModel().select(0, 0);
		}

		/*} else {
			this.dialog.show();		
		}*/

	}
});


Ext.form.ComplexTextField = Ext.extend(Ext.form.TriggerField, {

	defaultAutoCreate: { tag: "textarea", rows: 1, style: "height:16px;overflow:hidden;" },

    /**
     * If the trigger was clicked a dialog has to be opened
     * to enter the values for the complex property.
     */
	onTriggerClick: function () {

		if (this.disabled) {
			return;
		}

		var grid = new Ext.form.TextArea({
			anchor: '100% 100%',
			value: this.value,
			listeners: {
				focus: function () {
					this.facade.disableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
				}.bind(this)
			}
		})

		// Basic Dialog
		var dialog = new Ext.Window({
			layout: 'anchor',
			autoCreate: true,
			title: ORYX.I18N.PropertyWindow.text,
			height: 500,
			width: 500,
			modal: true,
			collapsible: false,
			fixedcenter: true,
			shadow: true,
			proxyDrag: true,
			keys: [{
				key: 27,
				fn: function () {
					dialog.hide()
				}.bind(this)
			}],
			items: [grid],
			listeners: {
				hide: function () {
					this.fireEvent('dialogClosed', this.value);
					dialog.destroy();
				}.bind(this)
			},
			buttons: [{
				text: ORYX.I18N.PropertyWindow.ok,
				handler: function () {
					// store dialog input
					var value = grid.getValue();
					this.setValue(value);

					this.dataSource.getAt(this.row).set('value', value)
					this.dataSource.commitChanges()

					dialog.hide()
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.cancel,
				handler: function () {
					this.setValue(this.value);
					dialog.hide()
				}.bind(this)
			}]
		});

		dialog.show();
		grid.render();

		this.grid.stopEditing();
		grid.focus(false, 100);

	}
});
Ext.AbstractWriter = Ext.extend(Ext.util.Observable, {
	constructor: function (config) {
		Ext.AbstractWriter.superclass.constructor.call(this, config);
		this.config = {};
		Object.extend(this.config, config)
	},
	setValue: function (value, path) {

	},
	getValue: function (values, path) {
		var props = path.split('.');
		if (values) {
			var ret
			if (props.length > 1 && values[props[0]]) //拥有 proptyp1.paroptyp2格式的类型path
			{
				var bexists = false;
				var obj = eval('(' + values[props[0]] + ')');
				obj.items.each(function (item) {
					if (item[this.config.idField] == this.config.key) {
						ret = item[this.config.valueField];
						bexists = true;
						return;
					}
				}.bind(this));
				//不存在就新增属性
				if (!bexists) {
					ret = this.config.dtype == 'Boolean' ? false : null;
				}
			}
			return ret;
		}
	}
});
Ext.DataPropertyWriter = Ext.extend(Ext.AbstractWriter, {
	constructor: function (config) {
		Ext.DataPropertyWriter.superclass.constructor.call(this, config);
		Object.extend(this.config, {
			idField: 'dataproperty_id'
			, nameField: 'dataproperty_name'
			, valueField: 'dataproperty_value'
			, typeField: 'dataproperty_type'
			, key: this.config.name.split('.')[1]
		})
	},
	setValue: function (value, path, isUpdate) {
		var proname = this.config.name.split('.')[0];
		var defvalue = {};
		defvalue[this.config.idField] = this.config.key;
		defvalue[this.config.nameField] = this.config.fieldLabel;
		defvalue[this.config.valueField] = value || '';
		defvalue[this.config.typeField] = this.config.dtype;
		if (this.config.propertywindow && this.config.propertywindow.shapeSelection.commonPropertiesValues[proname]) {
			var commonPropertiesValues = this.config.propertywindow.shapeSelection.commonPropertiesValues;
			var jsonstr = commonPropertiesValues[proname];
			var obj = Ext.decode(jsonstr ? jsonstr : '{}');
			var bexists = false;
			obj.items.each(function (item) {
				if (item[this.config.idField] == this.config.key) {
					item[this.config.valueField] = value || false;
					bexists = true;
					return;
				}
				//不存在就新增属性
			}.bind(this));
			if (!bexists) {
				obj.items.push(defvalue);
			}
			obj.totalCount = obj.items.size();
			var ret = Ext.encode(obj);
			if (isUpdate || isUpdate === undefined) {
				this.config.propertywindow.editDirectly(proname, ret);

			}
		}
	}
});
Ext.FormPropertyWriter = Ext.extend(Ext.AbstractWriter, {
	constructor: function (config) {
		Ext.FormPropertyWriter.superclass.constructor.call(this, config);
		Object.extend(this.config, {
			idField: 'formproperty_id'
			, nameField: 'formproperty_name'
			, valueField: 'formproperty_expression'
			, typeField: 'formproperty_type'
			, key: this.config.name.split('.')[1]
		})
	},
	setValue: function (value, path) {
		var proname = this.config.name.split('.')[0];
		var defvalue = {
			formproperty_expression: ""
			, formproperty_variable: ""
			, formproperty_required: "Yes"
			, formproperty_readable: "Yes"
			, formproperty_writeable: "Yes"
		};
		defvalue[this.config.idField] = this.config.key;
		defvalue[this.config.nameField] = this.config.fieldLabel;
		defvalue[this.config.valueField] = value || 'true';
		defvalue[this.config.typeField] = this.config.dtype || 'String';
		if (this.config.propertywindow && this.config.propertywindow.shapeSelection.commonPropertiesValues[proname] != undefined) {
			var commonPropertiesValues = this.config.propertywindow.shapeSelection.commonPropertiesValues;
			var jsonstr = commonPropertiesValues[proname];
			var obj = eval('(' + (jsonstr || '{}') + ')');
			var bexists = false;
			if (!obj.items) { obj.totalCount = 0; obj.items = []; }
			obj.items.each(function (item) {
				if (item[this.config.idField] == this.config.key) {
					item[this.config.valueField] = value || false;
					bexists = true;
					return;
				}
				//不存在就新增属性
			}.bind(this));
			if (!bexists) {
				obj.items.push(defvalue);
			}
			obj.totalCount = obj.items.size();
			var ret = Ext.encode(obj);
			this.config.propertywindow.editDirectly(proname, ret);
		}
	}
});
/**
 *  Tasklisteners
 */
Ext.TasklistenersWriter = Ext.extend(Ext.AbstractWriter, {
	constructor: function (config, propertywindow, editor) {
		Ext.TasklistenersWriter.superclass.constructor.call(this, config);
		Object.extend(this.config, {
		})
		this.editor = editor
		this.config.propertywindow = propertywindow;
	},
	setValue: function (store) {
		var proname = 'oryx-tasklisteners';
		var obj = {
			totalCount: 0
			, items: []
		}
		if (this.config.propertywindow) {
			store.each(function (record) {
				obj.items.push(Object.extend({}, record.data));
			});
			obj.totalCount = store.getCount();
			var ret = Ext.encode(obj);
			this.config.propertywindow.editDirectlyByShapes(this.editor.shapes, proname, ret);
		}
	},
});
/**
 *  TaskFromDataWriter
 */
Ext.TaskFromDataWriter = Ext.extend(Ext.AbstractWriter, {
	constructor: function (config, propertywindow, editor) {
		Ext.TaskFromDataWriter.superclass.constructor.call(this, config);
		Object.extend(this.config, {
		})
		this.config.propertywindow = propertywindow;
		this.editor = editor
		//this.shapeSelection = this.config.propertywindow.shapeSelection;
	},
	setValue: function (store) {
		var proname = 'oryx-formproperties';
		var obj = {
			totalCount: 0
			, items: []
		}
		if (this.config.propertywindow) {
			store.each(function (record) {
				obj.items.push(Object.extend({}, record.data));
			});
			obj.totalCount = store.getCount();
			var compare = function (obj1, obj2) {
				var val1 = obj1.formproperty_sort;
				var val2 = obj2.formproperty_sort;
				if (val1 < val2) {
					return -1;
				} else if (val1 > val2) {
					return 1;
				} else {
					return 0;
				}
			}
			obj.items.sort(compare);
			var ret = Ext.encode(obj);
			this.config.propertywindow.editDirectlyByShapes(this.editor.shapes, proname, ret);
		}
	},
});

/**
 * 弹出树形选择框 by gk
 */
Ext.form.SelectDialogBase = Ext.extend(Ext.form.TriggerField, {
	triggerClass: 'x-form-complex-trigger',
	readOnly: true,
	//构造函数
	constructor: function (config) {
		Ext.form.SelectDialogBase.superclass.constructor.call(this, config);
		var defaultConfig = {
			fieldId: ''
			, dataUrl: ''
			, fieldLabel: ''
			, propertywindow: undefined
		}
		this.key = this.name;
		this.config = this.config || {}
		Object.extend(this.config, defaultConfig);
		Object.extend(this.config, config)
		this.writer = new Ext.DataPropertyWriter(this.config);
		this.loadData();
	},
	/**
	 * 加载数据
	 */
	loadData() {
		var buildExtTreeData = function (data) {
			data.each(function (item) {
				item.id = item[this.config.treeId];
				item.text = item[this.config.treeName];
				item.pid = item[this.config.treePid];
			}.bind(this))
			return toTreeData(data)
		}.bind(this);
		if (!this.jsonData) {
			Ext.Ajax.request({
				url: this.config.dataUrl
				, async: false
				, method: 'GET'
				, success: function (response) {
					if (response.responseText.length > 0) {
						this.jsonData = Ext.decode(response.responseText).obj;
						this.treeData = buildExtTreeData(this.jsonData.slice(0));
					}
				}.bind(this)
				, failure: function () {
					this.jsonData = undefined;
				}.bind(this)
			});
		}
	},
	setValue: function (value, isUpdate) {
		this.selId = value;
		var displayText = this.__getDisplayText();
		Ext.form.SelectDialogBase.superclass.setValue.call(this, displayText);
		this.writer.setValue(this.selId, this.config.name, isUpdate);
	},
	//获取值显示的name
	__getDisplayText() {
		var ret = '';
		this.jsonData.each(function (item) {
			if (item.id == this.selId) {
				ret = item.text
				return;
			}
		}.bind(this));
		return ret;
	},
	onTriggerClick: function () {
		if (this.disabled) {
			return;
		}
		//构建功能选择选择树
		var root = new Ext.tree.AsyncTreeNode({
			text: '功能列表'
			, id: 'root'
			, expanded: true
			, children: this.treeData
		});
		var okhandle = function () {
			var selnode = tree.getSelectionModel().getSelectedNode();
			if (!selnode || !selnode.leaf) {
				return false;
			}
			this.setValue(selnode.attributes.id, true);
			dialog.hide();
			return true;
		}.bind(this);
		var tree = new Ext.tree.TreePanel({
			root: root
			, loader: new Ext.tree.TreeLoader()
			, anchor: '100% 100%'
			, border: false
			, autoScroll: true
			, animate: true
			, listeners: {
				dblclick: function (node) {
					okhandle();
				}.bind(this)
			}
		});
		var dialog = new Ext.Window({
			layout: 'anchor',
			autoCreate: true,
			title: '选择功能',
			height: 500,
			width: 500,
			modal: true,
			collapsible: false,
			fixedcenter: true,
			shadow: true,
			proxyDrag: true,
			keys: [{
				key: 27,
				fn: function () {
					dialog.hide()
				}.bind(this)
			}],
			items: [tree],
			listeners: {
				hide: function () {
					this.writer.setValue(this.selId, true);
					dialog.destroy();
				}.bind(this)
			},
			buttons: [{
				text: '确定',
				handler: okhandle
			}, {
				text: '取消',
				handler: function () {
					dialog.hide()
				}.bind(this)
			}]
		});
		dialog.show();
	}
});

/**
 * 功能选择 by gk
 */
Ext.form.FunctionSelectField = Ext.extend(Ext.form.SelectDialogBase, {
});
Ext.reg('functionfield', Ext.form.FunctionSelectField);

/**
 * 用户选择 by gk
 */
Ext.form.UserSelectField = Ext.extend(Ext.form.SelectDialogBase, {
	triggerClass: 'x-form-complex-trigger',
	readOnly: true,
	constructor: function (config) {
		Ext.form.UserSelectField.superclass.constructor.call(this, config);
	}
});
Ext.reg('userfield', Ext.form.FunctionSelectField);

/**
 * 角色选择 by gk
 */
Ext.form.RoleSelectField = Ext.extend(Ext.form.SelectDialogBase, {
});

/**
 * 部门选择 by gk
 */
Ext.form.DepartSelectField = Ext.extend(Ext.form.SelectDialogBase, {
});

/**
 * 通用checkbox勾选框 by gk
 */
Ext.form.CheckBoxField = Ext.extend(Ext.form.Checkbox, {
	//构造函数
	constructor: function (config) {
		Ext.form.CheckBoxField.superclass.constructor.call(this, config);
		var defaultConfig = {
			fieldId: ''
			, fieldLabel: ''
			, propertywindow: undefined
		}
		this.key = this.name;
		this.config = this.config || {}
		Object.extend(this.config, defaultConfig);
		Object.extend(this.config, config)
		if (this.name.indexOf('formproperties') != -1) {
			this.writer = new Ext.FormPropertyWriter(this.config);
		} else
			this.writer = new Ext.DataPropertyWriter(this.config);
	},
	setValue: function (value, isUpdate) {
		Ext.form.CheckBoxField.superclass.setValue.call(this, value);
		this.writer.setValue(value, this.config.name, isUpdate);
	}
});
Ext.reg('checkboxfield', Ext.form.CheckBoxField);

/**
 * 代码高亮编辑组件 by gk 
 * language 可以是以下的值
 * abap,abc,actionscript,ada,apache_conf,applescript,asciidoc,asl,assembly_x86,autohotkey,
 * batchfile,bro,c_cpp,c9search,cirru,clojure,cobol,coffee,coldfusion,csharp,csound_document,
 * csound_orchestra,csound_score,csp,css,curly,d,dart,diff,django,dockerfile,dot,drools,edifact,
 * eiffel,ejs,elixir,elm,erlang,forth,fortran,ftl,gcode,gherkin,gitignore,glsl,gobstones,golang,graphqlschema,
 * groovy,haml,handlebars,haskell_cabal,haskell,haxe,hjson,html_elixir,html_ruby,html,ini,io,jack,jade,java,
 * javascript,json,jsoniq,jsp,jssm,jsx,julia,kotlin,latex,less,liquid,lisp,livescript,logiql,lsl,lua,luapage,
 * lucene,makefile,markdown,mask,matlab,maze,mel,mixal,mushcode,mysql,nix,nsis,objectivec,ocaml,pascal,perl,pgsql,
 * php,pig,plain_text,powershell,praat,prolog,properties,protobuf,python,r,razor,rdoc,red,redshift,rhtml,rst,ruby,
 * rust,sass,scad,scala,scheme,scss,sh,sjs,smarty,snippets,soy_template,space,sparql,sql,sqlserver,stylus,svg,
 * swift,tcl,tex,text,textile,toml,tsx,turtle,twig,typescript,vala,vbscript,velocity,verilog,vhdl,wollok,xml,xquery,yaml,
 */
Ext.CodeEditorField = Ext.extend(Ext.form.TextArea, {
	constructor: function (config) {
		Ext.CodeEditorField.superclass.constructor.call(this, config);
		var defaultConfig = {
			language: 'groovy'
		}
		this.config = this.config || {}
		Object.extend(this.config, defaultConfig);
		Object.extend(this.config, config)
	},
	initValue: function () {
		if (this.el && !this.el.aceeditor) {
			this.el = new Ext.Element(this.el.dom.firstChild, false);
			this.el.aceeditor = this.createAceEditor(this.language);
			this.setValue(this.getValue())
		}
		if (this.el.aceeditor.getValue().length > 0) {
			this.setValue(this.getValue());
		}
	},

	createAceEditor: function (language) {
		ace.require("ace/ext/language_tools");
		var editor = ace.edit(this.getPerId());
		editor.setTheme("ace/theme/twilight");
		editor.session.setMode("ace/mode/" + language);
		editor.setFontSize(14);
		editor.setReadOnly(false);
		//自动换行,设置为off关闭
		//editor.setOption("wrap", "free");
		editor.setOptions({
			enableBasicAutocompletion: true,
			enableSnippets: true,
			enableLiveAutocompletion: true
		});
		editor.getSession().on('change', function (e, editor) {
			var v = editor.getValue();
			Ext.form.TextField.superclass.setValue.call(this, v);
		}.bind(this));
		return editor;
	},
	setLanguage: function (v) {
		if (this.el.aceeditor) {
			this.el.aceeditor.session.setMode("ace/mode/" + v);
		}
		this.language = v;
	},
	setValue: function (v) {
		Ext.form.TextField.superclass.setValue.call(this, v);
		if (this.el.aceeditor) {
			this.el.aceeditor.setValue(v);
		}
		this.setLanguage(this.language);
	},
	initEvents: function () {
		this.el.on(Ext.isIE ? "keydown" : "keypress", this.fireKey, this);
		this.el.on("focus", this.onFocus, this);
		this.el.on("blur", this.onBlur, this);
		this.originalValue = this.getValue();
	},
	getValue: function () {
		if (!this.rendered) {
			return this.value;
		}
		var v = this.el.aceeditor.getValue();
		if (v === this.emptyText || v === undefined) {
			v = '';
		}
		return v;
	},
	getPerId: function () {
		return 'per_' + this.name || this.id;
	},
	onRender: function (ct, position) {
		if (!this.el) {
			this.defaultAutoCreate = {
				tag: "pre"
				, style: "min-height:400px;"
				, id: this.getPerId()
				, children: {
					tag: "textarea"
					, name: this.name || this.id
					, style: "width:100%;height:100%;margin-left:1px;"
					, autocomplete: "off"
				}
			};
		}
		Ext.form.TextArea.superclass.onRender.call(this, ct, position);
	}
});
Ext.reg('codefield', Ext.CodeEditorField);

/**
 * 脚本编辑组件 by gk
 * 这个组件修改时候是即时更新对应的json格式
 */
Ext.ScriptEditorField = Ext.extend(Ext.CodeEditorField, {
	initValue: function () {
		Ext.ScriptEditorField.superclass.initValue.call(this);
		this.el.aceeditor.getSession().on('change', function (e, editor) {
			var v = editor.getValue();
			this.config.propertywindow.editDirectly(this.name, v);
		}.bind(this));
	}
});
Ext.reg('scriptfield', Ext.ScriptEditorField);

/**通用form类 */
Ext.FlowForm = Ext.extend(Ext.form.BasicForm, {
	constructor: function (el, config) {
		Ext.FlowForm.superclass.constructor.call(this, el, config);
	},
	/**
	 * 覆盖父类的loadRecord 函数
	 */
	loadRecord: function (record) {
		this.setValues(record.data);
	},
	/**
	 * 覆盖父类的setValues函数
	 * 让所有同明的field都赋值
	 */
	setValues: function (values) {
		if (Ext.isArray(values)) {
			for (var i = 0, len = values.length; i < len; i++) {
				var v = values[i];
				var f = this.findField(v.id);
				if (f) {
					f.setValue(v.value);
					if (this.trackResetOnLoad) {
						f.originalValue = f.getValue();
					}
				}
			}
		} else {
			var field;
			for (id in values) {
				if (typeof values[id] != 'function' && (field = this.findField(id))) {
					field.setValue(values[id]);
					if (this.trackResetOnLoad) {
						field.originalValue = field.getValue();
					}
				}
			}
		}
		this.items.each(function (f) {
			if (f.writer) {
				var val = f.writer.getValue(values, f.name);
				f.setValue(val, false);
			}
		});
		return this;
	}
});


/**
 * 通用editor构造器 by gk
 */
Ext.CommonEditorBuilder = Ext.extend(function () { }, {
	//构造函数
	constructor: function () {
		Ext.CommonEditorBuilder.superclass.constructor.call(this);
	},
	/**
	 * 构建id对应的editor  by gk
	 */
	buildEditor: function (id, propertywindow) {
		if (id.indexOf('UserTask') > 0) {
			return new Ext.UserTaskCommonEditor({}, propertywindow);
		}
		else if (id.indexOf('BPMNDiagram') > 0) {
			return new Ext.WorkFlowCommonEditor({}, propertywindow);
		} else if (id.indexOf('SequenceFlow') > 0) {
			return new Ext.LineCommonEditor({}, propertywindow);
		} else if (id.indexOf('ServiceTask') > 0) {
			return new Ext.ServerTaskCommonEditor({}, propertywindow);
		} else if (id.indexOf('ScriptTask') > 0) {
			return new Ext.ScriptTaskCommonEditor({}, propertywindow);
		} else if (id.indexOf('MailTask') > 0) {
			return new Ext.MailTaskCommonEditor({}, propertywindow);
		}
		else
			return new Ext.DefaultCommonEditor({}, propertywindow);
	}
});

/**
 * 扩展的通用编辑panel组件基类 by gk
 */
Ext.AbstractCommonEditorPanel = Ext.extend(Ext.form.FormPanel, {
	//构造函数
	constructor: function (config, propertywindow) {
		this.propertywindow = propertywindow;
		Ext.AbstractCommonEditorPanel.superclass.constructor.call(this, config);
	},
	//覆盖了父类的createFormh函数,使用自定义的form
	createForm: function () {
		delete this.initialConfig.listeners;
		return new Ext.FlowForm(null, this.initialConfig);
	}
});

/**
 * 策略编辑器基类
 */
Ext.form.StrategysEditor = Ext.extend(Ext.form.ComboBox, {
	triggerClass: 'x-form-search-trigger',
	constructor: function (config, owner) {
		this.owner = owner;
		this.propertywindow = config.propertywindow;
		var initconfig = {};
		Object.extend(initconfig, config);
		Ext.form.StrategysEditor.superclass.constructor.call(this, initconfig);
	},
	/**
	 * 获取当前的编辑的记录 by gk
	 */
	getRecord: function () {
		var record = this.getGrid().getSelectionModel().selection.record;
		return record;
	},
	/**
	 * 获取对应的grid
	 */
	getGrid: function () {
		return this.owner.StrategyGrid;
	},
	/**
	 * 构建不同策略类型的显示窗口
	 */
	initEditorPanel: function (type) {
		var record = this.getRecord();
		if (record.data['task_listener_stype'] == 'selPer' || record.data['task_listener_stype'] == 'selHuiq') {
			return new Ext.SelectPersonStrategysEditor(this.config, this.getGrid(), record.data['task_listener_stype'], this.propertywindow);
		} else if (record.data['task_listener_stype'] == 'selTemp') {
			return new Ext.SelectTempleteStrategysEditor(this.config, this.getGrid(), record.data['task_listener_stype'], this.propertywindow);
		} else if (record.data['task_listener_stype'] == 'userdef') {
			return new Ext.UserDefinedStrategysEditor(this.config, this.getGrid(), this.propertywindow);
		}
		return new Ext.Panel();
	},
	getValue() {
		var ret = Ext.form.ComboBox.superclass.getValue.call(this);
		if (ret.startsWith('<div'))
			ret = '';
		return ret;
	},
	onTriggerClick: function () {
		if (this.disabled) {
			return;
		}
		var popItem = this.initEditorPanel();
		var dialog = new Ext.Window({
			layout: 'anchor',
			autoCreate: true,
			title: '策略设置选择',
			height: popItem.initHeight,
			width: popItem.initWidth,
			modal: true,
			collapsible: false,
			fixedcenter: true,
			shadow: true,
			proxyDrag: true,
			keys: [{
				key: 27,
				fn: function () {
					dialog.hide()
				}.bind(this)
			}],
			items: [popItem],
			listeners: {
				hide: function () {
					dialog.destroy();
				}.bind(this),
				show: function () {
					if (popItem.onShow)
						popItem.onShow()
				}.bind(this)

			},
			buttons: [{
				id: 'btnStrategysEditorOk'
				, text: '确定'
				, listeners: {
					click: function () {
						if (popItem.okCallBack && popItem.okCallBack()) {
							dialog.hide()
						}
					}
				}
			}, {
				id: 'btnStrategysEditorCancel'
				, text: '取消'
				, listeners: {
					click: function () {
						if (popItem.cancelCallBack && popItem.cancelCallBack()) {
							dialog.hide()
						}
					}
				}
			}]
		});
		dialog.show();
	}
});

/**
 * 策略编辑的panel的基类
 */
Ext.StrategysEditorPanelBase = Ext.extend(Ext.Panel, {
	anchor: '100%,100%'
	, initWidth: 1024 //初始化的窗口宽度
	, initHeight: 768  //初始化的窗口高度
	, constructor: function (config, grid) {
		this.grid = grid;
		Ext.StrategysEditorPanelBase.superclass.constructor.call(this, config);
	},
	onShow: function () {

	},
	/**
	 * 获取当前的编辑的记录 by gk
	 */
	getRecord: function () {
		var record = this.grid.getSelectionModel().selection.record;
		return record;
	},
	cancelCallBack: function () {
		return true;
	},
	okCallBack: function () {
		return true;
	}
});
//选择人员策略
Ext.SelectPersonStrategysEditor = Ext.extend(Ext.StrategysEditorPanelBase, {
	initWidth: 1224
	, initHeight: 768
	, pageSize: 100
	, constructor: function (config, grid, datatype, propertywindow) {
		this.propertywindow = propertywindow;
		this.datatype = datatype;
		this.grid = grid;
		this.fields = [
			{ name: 'USERID', type: 'string' }
			, { name: 'DEPTID', type: 'string' }
			, { name: 'ROLEID', type: 'string' }
			, { name: 'ORGID', type: 'string' }
			, { name: 'ZHANGHM', type: 'string' }
			, { name: 'ZHANGHM', type: 'string' }
			, { name: 'OBJNAME', type: 'string' }
			, { name: 'GONGH', type: 'string' }
			, { name: 'SHOUJH', type: 'string' }
			, { name: 'DEPTNAME', type: 'string' }
			, { name: 'ROLENAME', type: 'string' }
			, { name: 'ORGNAME', type: 'string' }
		];
		var initconfig = {};
		Object.extend(initconfig, config);
		var eastGrid = this.createEastGrid();
		var westTab = this.createWestTab();
		var centerGrid = this.createCenterGrid();
		Object.extend(initconfig, {
			anchor: '100% 100%'
			, layout: 'border'
			, defaults: {
				collapsible: true
				, split: true
				, bodyStyle: 'padding:0px'
			}
			, items: [{
				region: 'center'
				, layout: 'fit'
				, collapsible: false
				, items: [centerGrid]
			}, {
				region: 'west'
				, width: 300
				, items: [westTab]
			}, {
				title: '已选择'
				, region: 'east'
				, layout: 'fit'
				, width: 400
				, collapsible: false
				, items: [eastGrid]
			}]
		});
		Ext.SelectPersonStrategysEditor.superclass.constructor.call(this, initconfig, grid);
	},
	createWestTab: function () {
		var buildTreeData = function (data) {
			data.each(function (item) {
				item.id = item['RWID'];
				item.text = item['OBJNAME'];
				item.pid = item['SYS_PARENTID'] || '';
				Object.extend(item, item);
			}.bind(this))
			return toTreeData(data)
		}.bind(this);
		var buildTreeNodes = function (data) {
			data.each(function (item) {
				item.id = item['RWID'];
				item.text = item['OBJNAME'];
				item.pid = item['SYS_PARENTID'] || '';
				Object.extend(item, item);
			}.bind(this))
			return toTreeNodes(data)
		}.bind(this);
		if (!this.roleData) {
			Ext.Ajax.request({
				url: getContextPath() + "/manager/coreroleauth/queryRole?ORGID=" + getCurrentOrgId()
				, async: false
				, method: 'GET'
				, success: function (response) {
					if (response.responseText.length > 0) {
						this.roleData = Ext.decode(response.responseText).obj;
						this.roletreeData = buildTreeData(this.roleData.slice(0));
					}
				}.bind(this)
				, failure: function () {
					this.roleData = undefined;
				}.bind(this)
			});
		}
		if (!this.deptData) {
			Ext.Ajax.request({
				url: getContextPath() + "/admin/coreorg/queryorgdepttreelistbyparentid?ORGID=" + getCurrentOrgId()
				, async: false
				, method: 'POST'
				, success: function (response) {
					if (response.responseText.length > 0) {
						this.deptData = Ext.decode(response.responseText).obj;
						this.depttreeData = buildTreeData(this.deptData.slice(0));
					}
				}.bind(this)
				, failure: function () {
					this.deptData = undefined;
				}.bind(this)
			});
		}

		var roleRoot = new Ext.tree.AsyncTreeNode({
			text: '所有角色'
			, id: 'roleroot'
			, expanded: true
			, children: this.roletreeData
		});
		addDept = function (orgNode) {
			if (!orgNode.hasChildNodes()) {
				Ext.Ajax.request({
					url: getContextPath() + '/admin/coreorg/queryorgdepttreelistbyparentid?RWID=' + orgNode.id
					, async: false
					, method: 'POST'
					, success: function (response) {
						if (response.responseText.length > 0) {
							var dataObj = Ext.decode(response.responseText);
							if (dataObj.state == 'OK') {
								var deptsNodes = buildTreeNodes(dataObj.obj.slice(0));
								for (i = 0; i < deptsNodes.length; i++) {
									orgNode.appendChild(deptsNodes[i]);
								}
								orgNode.leaf = false;
								orgNode.expand(true, true);
							}
							else {
								alert(dataObj.msg);
							}
						}
					}.bind(this)
					, failure: function (e) {
						alert(e);
					}.bind(this)
				});
			}
		}.bind(this);
		var nodeClick = function (node) {
			if (!node) {
				return false;
			}
			if (tabs.getActiveTab().getId() == 'tabRole') {
				this.dsUser.load({ params: { start: 0, limit: this.pageSize, ORGID: getCurrentOrgId(), ROLEID: node.id } })
			} else if (tabs.getActiveTab().getId() == 'tabDept') {
				if (node.attributes.data && node.attributes.data.EXPANDPROPERTY && node.attributes.data.EXPANDPROPERTY == 'org') {
					this.dsUser.load({ params: { start: 0, limit: this.pageSize, ORGID: node.id } })
					addDept(node);
				} else {
					this.dsUser.load({ params: { start: 0, limit: this.pageSize, DEPTID: node.id } })
					addDept(node);
				}
			}
			return true;
		}.bind(this);
		var roleTree = new Ext.tree.TreePanel({
			root: roleRoot
			, loader: new Ext.tree.TreeLoader()
			, border: false
			, containerScroll: true
			, animate: true
			, listeners: {
				click: nodeClick
			}
		});

		var deptRoot = new Ext.tree.AsyncTreeNode({
			text: '所有部门'
			, id: 'deptroot'
			, expanded: true
			, children: this.depttreeData
		});

		var deptTreeLoader = new Ext.tree.TreeLoader({
			dataUrl: getContextPath() + '/admin/coreorg/queryorgdepttreelistbyparentid'
		});
		var deptTree = new Ext.tree.TreePanel({
			root: deptRoot
			, loader: deptTreeLoader
			, border: false
			, containerScroll: true
			, animate: true
			, listeners: {
				click: nodeClick
			}
		});
		deptTreeLoader.on('beforeload', function (loader, node) {
			this.baseParams.id = node.id; //通过这个传递参数，这样就可以点一个节点出来它的子节点来实现异步加载  
		}, deptTreeLoader);

		var tabs = new Ext.TabPanel({
			activeTab: 0
			, resizeTabs: false
			, height: this.initHeight - 76
			, minTabWidth: 100
			, tabWidth: 80
			, defaults: {
				autoScroll: true
			}
			, items: [{
				id: 'tabRole'
				, title: '角色'
				, items: [roleTree]
			}, {
				id: 'tabDept'
				, title: '机构部门'
				, items: [deptTree]
			}
			]
			, listeners: {
				tabchange: function (tabs, tab) {
					tab.doLayout();
				}
			}
		});
		return tabs;
	},
	createCenterGrid: function () {
		this.dsUser = new Ext.data.Store({
			totalProperty: "obj",
			proxy: new Ext.data.HttpProxy({
				method: 'POST'
				, url: getContextPath() + '/admin/coreuser/queryusersbydeptrole'
				, extraParams: { page: 1 }
			})
			, reader: new Ext.data.JsonReader({
				root: 'obj'
				, totalProperty: 'total'
			}, this.fields)
		});
		var pageToolbar = new Ext.PagingToolbar({
			pageSize: this.pageSize
			, store: this.dsUser
			, displayInfo: true
			, displayMsg: '第{0} 到 {1} 条数据 共{2}条'
			, emptyMsg: "没有数据"
			, beforePageText: "页数"
			, afterPageText: "共{0}页"
		})
		//设置分页
		this.dsUser.on('beforeload', function (store, options) {
			var params = {
				page: options.params.start ? (options.params.start / options.params.limit) + 1 : 1,
				pageSize: this.pageSize
			};
			Ext.apply(store.baseParams, params);
		}.bind(this));
		//this.dsUser.load({ params: { start: 0, limit: pageSize } });

		//定义勾选框，不需要可不必定义   
		var sm = new Ext.grid.CheckboxSelectionModel();
		sm.handleMouseDown = Ext.emptyFn;//不响应MouseDown事件
		sm.on('rowselect', function (sm_, rowIndex, record) {//行选中的时候
			var dataIndex = this.dsSelections.findBy(function (r, id) {
				var ret = r.get('USERID') == record.get('USERID')
					&& r.get('ROLEID') == record.get('ROLEID')
					&& r.get('DEPTID') == record.get('DEPTID')
				return ret;
			});
			if (dataIndex == -1) {
				this.dsSelections.insert(0, record);
				this.dsSelections.commitChanges();
				this.eastGrid.getView().refresh();
			}
		}, this);
		sm.on('rowdeselect', function (sm_, rowIndex, record) {//行未选中的时候
			var dataIndex = this.dsSelections.findBy(function (r, id) {
				var ret = r.get('USERID') == record.get('USERID')
					&& r.get('ROLEID') == record.get('ROLEID')
					&& r.get('DEPTID') == record.get('DEPTID')
				return ret;
			});
			if (dataIndex != -1) {
				var r = this.dsSelections.getAt(dataIndex);
				this.dsSelections.remove(r);
				this.dsSelections.commitChanges();
				this.eastGrid.getView().refresh();
			}
		}, this);
		this.centerGrid = new Ext.grid.EditorGridPanel({
			clicksToEdit: 1
			, anchor: '100% 100%'
			, stripeRows: true
			, sm: sm
			, cm: new Ext.grid.ColumnModel([
				new Ext.grid.RowNumberer({ header: '#', width: 30 })
				, new Ext.grid.CheckboxSelectionModel()
				, {
					header: '登录账户名'
					, dataIndex: 'ZHANGHM'
					, width: 120
					, sortable: true
				}, {
					header: '姓名'
					, dataIndex: 'OBJNAME'
					, width: 120
					, sortable: true
				}, {
					header: '部门'
					, dataIndex: 'DEPTNAME'
					, width: 120
					, sortable: true
				}, {
					header: '角色'
					, dataIndex: 'ROLENAME'
					, width: 120
					, sortable: true
				}, {
					header: '机构'
					, dataIndex: 'ORGNAME'
					, width: 120
					, sortable: true
				}, {
					header: '工号'
					, dataIndex: 'GONGH'
					, width: 120
					, sortable: true
				}, {
					header: '手机号'
					, dataIndex: 'SHOUJH'
					, width: 120
					, sortable: true
				}
			])
			, enableHdMenu: false
			, store: this.dsUser
			, bbar: pageToolbar
		});
		return this.centerGrid;
	},
	createEastGrid: function () {
		this.dsSelections = new Ext.data.Store({
			proxy: new Ext.data.MemoryProxy(eval("({})")),
			reader: new Ext.data.JsonReader({
				root: 'items',
				totalProperty: 'totalCount'
			}, this.fields)
		});
		var pageToolbar = new Ext.PagingToolbar({
			pageSize: 5000
			, store: this.dsSelections
			, displayInfo: false
			, displayMsg: '共{2}条'
			, emptyMsg: "没有数据"
			, onFirstLayout: function () {//增加这个配置
				if (this.rendered && this.refresh) {
					this.refresh.hide();
				}
			}
		});
		this.dsSelections.load();
		var sm = new Ext.grid.CheckboxSelectionModel();
		this.eastGrid = new Ext.grid.EditorGridPanel({
			id: 'gridSelect'
			, clicksToEdit: 1
			, anchor: '100% 100%'
			, stripeRows: true
			, sm: sm
			, colModel: new Ext.grid.ColumnModel([
				new Ext.grid.RowNumberer({ header: '#', width: 30 })
				, new Ext.grid.CheckboxSelectionModel()
				, {
					header: '姓名'
					, dataIndex: 'OBJNAME'
					, width: 40
					, sortable: true
				}, {
					header: '账户名'
					, dataIndex: 'ZHANGHM'
					, width: 40
					, sortable: true
				},
				{
					header: '部门',
					width: 50,
					dataIndex: 'DEPTNAME',
					align: 'center',
					sortable: false,
				},
				{
					header: '角色',
					width: 50,
					dataIndex: 'ROLENAME',
					align: 'center',
					sortable: false,
				},
				{
					header: '机构',
					width: 80,
					dataIndex: 'ORGNAME',
					align: 'center',
					sortable: false,
				}
			])
			, enableHdMenu: false
			, store: this.dsSelections
			, viewConfig: {
				forceFit: true
				, scrollOffset: 0
			}
			//, bbar: pageToolbar
			, tbar: [
				{
					text: '移除',
					icon: getContextPath() + '/static/workflow/editor/images/delete.png',
					iconCls: "x-dummy",
					handler: function () {
						var grid = Ext.getCmp('gridSelect');
						if (grid.getSelectionModel().selections.length == 0) {
							Ext.Msg.alert("提示", "请选中需移除的项");
							return;
						}
						var sm = grid.getSelectionModel();
						var selectRows = sm.getSelections();
						for (i = 0; i < selectRows.length; i++) {
							grid.getStore().remove(selectRows[i]);
						}
					}

				}
			]
		});
		return this.eastGrid;
	},
	okCallBack: function () {
		if (this.dsSelections.getCount() > 0) {
			var datatype = this.datatype;
			var record = this.getRecord();
			var selectPerson = [], selectUser = [], selecthuiq = [], tmpfield;
			var getUserString = function (record) {
				return record.get('ORGID') + '_' + record.get('DEPTID') + '_' + record.get('ROLEID') + '_' + record.get('USERID');
				// return record.get('USERID');
			}
			var getUserDisplayTitle = function (record) {
				return record.get('OBJNAME');
			}
			this.dsSelections.each(function (record) {
				var obj = { "assignment_type": "assignee", "resourceassignmentexpr": getUserString(record) };
				selectPerson.push(getUserString(record));
				selectUser.push(getUserDisplayTitle(record));
				selecthuiq.push(obj);
			});
			record.set('task_listener_class', 'com.kdayun.z1.core.workflow.listerner.usertask.PersonRoleDeptListenerImpl');
			if (datatype == 'selHuiq') {
				var datastring = "users" + new Date().getTime();
				record.set('task_listener_class', 'com.kdayun.z1.core.workflow.listerner.usertask.CountersignListener');
				this.propertywindow.editDirectly('oryx-multiinstance_sequential', 'No');
				this.propertywindow.editDirectly('oryx-multiinstance_cardinality', selecthuiq.length);
				this.propertywindow.editDirectly('oryx-multiinstance_variable', datastring);
				this.propertywindow.editDirectly('oryx-multiinstance_collection', '${CountersignListener.getUsers(execution)}');
				tmpfield = {
					totalCount: 1
					, items: [{ 'assignment_type': 'assignee', 'resourceassignmentexpr': '${' + datastring + '}' }]
				}
				this.propertywindow.editDirectly('oryx-usertaskassignment', Ext.encode(tmpfield));
			} else {
				this.propertywindow.editDirectly('oryx-multiinstance_condition', '');
			}
			tmpfield = {
				totalCount: 1,
				items: [{
					task_listener_field_name: 'condition'
					, task_listener_field_value: " USERID in('" + selectPerson.join("','") + "')"
				}
				]
			}
			record.set('task_listener_displaytitle', selectUser.join(','));
			record.set('task_listener_fields', Ext.encode(tmpfield));

			record.commit();
			return true;
		}
	}
});
//选择样板策略
Ext.SelectTempleteStrategysEditor = Ext.extend(Ext.StrategysEditorPanelBase, {
	constructor: function (config, grid, datatype, propertywindow) {
		var initconfig = {};
		this.datatype = datatype;
		this.propertywindow = propertywindow;
		this.grid = grid;
		Object.extend(initconfig, config || {
			anchor: '100% 100%'
			, layout: 'border'
			, defaults: {
				collapsible: true
				, split: true
				, bodyStyle: 'padding:0px'
			}
			, items: [{
				region: 'center'
				, layout: 'fit'
				, collapsible: false
				, items: [this.initEditorPanel()]
			}]
		});
		Ext.SelectTempleteStrategysEditor.superclass.constructor.call(this, initconfig, grid);
	},
	initEditorPanel: function () {
		this.fields = [
			{ name: 'VALUE', type: 'string' }
			, { name: 'CODE', type: 'string' }
			, { name: 'DCMX_KEY', type: 'string' }
		]
		this.dsSelections = new Ext.data.Store({
			totalProperty: "obj",
			proxy: new Ext.data.HttpProxy({
				url: getContextPath() + '/manager/coredictionary/querydictionarybyid?RWID=70F0BB4839D943C7BF7E4C393B1306E5'
			})
			, reader: new Ext.data.JsonReader({
				root: 'obj'
				, totalProperty: 'total'
			}, this.fields)
		});

		this.dsSelections.load();
		var sm = new Ext.grid.RowSelectionModel({
			singleSelect: true
		});
		var editformPanel = new Ext.form.FormPanel({
			anchor: '100% 100%'
			, bodyStyle: 'padding:10px'
			, items: [
				{
					fieldLabel: '名称'
					, anchor: '100%'
					, xtype: 'textfield'
					, name: 'VALUE'
					, labelStyle: 'text-align:right;'
				}, {
					fieldLabel: '策略值'
					, anchor: '100%'
					, height: 600
					, xtype: 'codefield'
					, language: 'json'
					, name: 'CODE'
					, labelStyle: 'text-align:right;'
				}
			]
		})
		//编辑窗口
		var editWindow = new Ext.Window({
			layout: 'anchor'
			, width: 800
			, height: 500
			, modal: true
			, collapsible: false
			, fixedcenter: true
			, autoDestroy: false
			, shadow: true
			, proxyDrag: true
			, items: editformPanel
			, listeners: {
				show: function () {
					if (this.editFlag == 'new') {
						editformPanel.getForm().reset();
						editWindow.setTitle('新增');
					}
					else {
						var grid = Ext.getCmp('gridTempleteStrategsy');
						if (grid.getSelectionModel().selections.length > 0) {
							editWindow.setTitle('编辑');
							editformPanel.getForm().reset();
							editformPanel.getForm().loadRecord(grid.getSelectionModel().selections.get(0));
						}
					}
				}.bind(this)
			}
			, buttons: [{
				id: 'btnSelectTempleteStrategysEditorOk'
				, text: '确定'
				, listeners: {
					click: function () {
						var form = editformPanel.getForm();
						var title = form.findField("VALUE").getValue();
						var value = form.findField("CODE").getValue();
						if (!value) {
							Ext.Msg.alert('提示', '请填写策略值！');
							return;
						}
						var that = this;
						var param = {};
						if (this.editFlag == 'new') {

							param.ZDZ_DICTIONARYID = '70F0BB4839D943C7BF7E4C393B1306E5';
							param.ZDZ_MINGC = title
							param.ZDZ_ZIDZ = value;
							param.ZDZ_SHIFQY = "1";
							Ext.Ajax.request({
								url: getContextPath() + '/manager/coredictionary/addmx',// 要跳转的url,此为属性必须要有
								method: 'put',
								headers: { 'Content-Type': 'application/json' },
								params: Ext.encode(param),
								success: function (result) {
									if (Ext.decode(result.responseText).state == 'OK') {
										Ext.Msg.alert('提示', '新增成功');
										that.dsSelections.load();
									} else {
										Ext.Msg.alert('提示', '新增失败' + result.msg);
									}
								},
								failure: function () {
									Ext.Msg.alert('保存失败！');
								}
							});
						} else {
							var selectdata = Ext.getCmp('gridTempleteStrategsy').getSelectionModel().selections.get(0);
							param.ZDZ_MINGC = title
							param.ZDZ_ZIDZ = value;
							param.RWID = selectdata.json.DCMX_KEY;
							param.ZDZ_DICTIONARYID = '70F0BB4839D943C7BF7E4C393B1306E5';
							selectdata.data.CODE = value;
							selectdata.data.VALUE = title;
							// selectdata.data.update();
							Ext.Ajax.request({
								url: getContextPath() + '/manager/coredictionary/modifymx',// 要跳转的url,此为属性必须要有
								method: 'POST',
								dataType: 'json',
								contentType: "application/json",
								headers: { 'Content-Type': 'application/json' },
								params: Ext.encode(param),
								success: function (result) {
									if (Ext.decode(result.responseText).state == 'OK') {
										Ext.Msg.alert('提示', '保存成功');
										// selectdata.data.update();
										// Ext.that('gridTempleteStrategsy').commitChanges().
										that.dsSelections.load();
									} else {
										Ext.Msg.alert('提示', '保存失败' + result.msg);
									}

								},
								failure: function () {
									Ext.Msg.alert('保存失败！');
								}
							});
						}
						editWindow.hide()
					}.bind(this)
				}
			}, {
				id: 'btnSelectTempleteStrategysEditorCancel'
				, text: '取消'
				, listeners: {
					click: function () {
						editWindow.hide()
					}.bind(this)
				}
			}]
		});
		//工具栏
		var toolbar = new Ext.Toolbar(
			[{
				text: ORYX.I18N.PropertyWindow.add,
				icon: getContextPath() + '/static/workflow/editor/images/add.png',
				iconCls: "x-dummy",
				handler: function () {
					this.editFlag = 'new';
					editWindow.zseed = 99999;
					editWindow.show();
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.modify,
				icon: getContextPath() + '/static/workflow/editor/images/modify.png',
				iconCls: "x-dummy",
				handler: function () {
					this.editFlag = 'edit';
					editWindow.zseed = 99999;
					editWindow.show();
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.rem,
				icon: getContextPath() + '/static/workflow/editor/images/delete.png',
				iconCls: "x-dummy",
				handler: function () {
					var grid = Ext.getCmp('gridTempleteStrategsy');
					if (grid.getSelectionModel().selections.length == 0) {
						Ext.Msg.alert("提示", "请选中要删除的策略模板");
						return;
					}
					Ext.MessageBox.confirm('提示', '确实要删除所选的策模板吗？', function (btn) {
						if (btn == 'yes') {
							grid.stopEditing();
							var selectdata = Ext.getCmp('gridTempleteStrategsy').getSelectionModel().selections.get(0);
							var param = {};
							param.id = selectdata.json.DCMX_KEY;
							// selectdata.data.update();
							Ext.Ajax.request({
								url: getContextPath() + '/manager/coredictionary/removemx',// 要跳转的url,此为属性必须要有
								method: 'DELETE',
								dataType: 'json',
								contentType: "application/json",
								headers: { 'Content-Type': 'application/json' },
								params: Ext.encode(param),
								success: function (result) {
									if (Ext.decode(result.responseText).state == 'OK') {
										Ext.Msg.alert('提示', '删除成功');
										grid.getStore().remove(selectdata);
									} else {
										Ext.Msg.alert('提示', '删除失败' + result.msg);
									}
								},
								failure: function () {
									Ext.Msg.alert('删除失败');
								}
							});

						}
					});
				}.bind(this)
			}
			]);
		this.selectionsGrid = new Ext.grid.EditorGridPanel({
			clicksToEdit: 1
			, id: 'gridTempleteStrategsy'
			, stripeRows: true
			, anchor: '100% 100%'
			, selModel: sm
			, colModel: new Ext.grid.ColumnModel([
				new Ext.grid.RowNumberer()
				, {
					header: '策略模板名称'
					, dataIndex: 'VALUE'
					, width: 120
					, sortable: true
				}, {
					header: '策略模板值'
					, dataIndex: 'CODE'
					, width: 120
					, sortable: true
				}
			])
			, viewConfig: {
				forceFit: true
				, scrollOffset: 0
			}
			, enableHdMenu: false
			, store: this.dsSelections
			, tbar: toolbar
		});
		this.selectionsGrid.addListener('rowdblclick', function (grid, rowindex, e) {
			Ext.getCmp('btnStrategysEditorOk').fireEvent('click');
		});
		return this.selectionsGrid;
	},
	okCallBack: function () {
		if (this.selectionsGrid.getSelectionModel().selections.length > 0) {
			var selRecord = this.selectionsGrid.getSelectionModel().selections.get(0);
			var record = this.getRecord();
			try {
				var obj = eval('(' + (selRecord.get('CODE') || {}) + ')');
			} catch (e) {
				alert('策略值格式错误:' + e);
				return;
			}


			var tmpobj = {
				totalCount: 0
				, items: []
			}
			var tmpfield = {
				totalCount: 0,
				items: []
			}
			obj.params.each(function (param) {
				tmpfield.items.push({
					task_listener_field_name: param.name
					, task_listener_field_value: param.value
				})
			})
			tmpfield.totalCount = obj.params.length;
			this.propertywindow.editDirectly('oryx-multiinstance_condition', '');
			record.set('task_listener_class', obj.class);
			record.set('task_listener_displaytitle', selRecord.get('VALUE'));
			record.set('task_listener_fields', Ext.encode(tmpfield));
			record.commit();
			return true;
		}
	}
});
//用户自定义策略
Ext.UserDefinedStrategysEditor = Ext.extend(Ext.StrategysEditorPanelBase, {
	initHeight: 540  //初始化的窗口高度
	, constructor: function (config, grid, propertywindow) {
		this.propertywindow = propertywindow;
		var initconfig = {};
		this.grid = grid;
		Object.extend(initconfig, config || {
			anchor: '100% 100%'
			, layout: 'border'
			, defaults: {
				collapsible: true
				, split: true
				, bodyStyle: 'padding:0px'
			}
			, items: [{
				region: 'center'
				, layout: 'fit'
				, collapsible: false
				, items: [this.initEditorPanel()]
			}]
		});
		Ext.UserDefinedStrategysEditor.superclass.constructor.call(this, initconfig, grid);
	},
	initEditorPanel: function () {
		this.editformPanel = new Ext.form.FormPanel({
			anchor: '100% 100%'
			, bodyStyle: 'padding:10px'
			, items: [
				{
					anchor: '100%'
					, fieldLabel: '名称'
					, xtype: 'textfield'
					, name: 'name'
					, labelStyle: 'text-align:right;'
				}, {
					anchor: '100%'
					, fieldLabel: '策略值'
					, height: 500
					, xtype: 'codefield'
					, language: 'json'
					, name: 'value'
					, labelStyle: 'text-align:right;'
				}]
		})
		this.editformPanel.getForm().reset();

		return this.editformPanel;
	},
	onShow: function () {
		var record = this.getRecord();
		var fields = eval('(' + record.get('task_listener_fields') + ')');
		var tmp = {
			name: record.get('task_listener_displaytitle'),
			class: record.get('task_listener_class'),
			params: []
		}
		if (fields && fields.items) {
			fields.items.each(function (field) {
				tmp.params.push({ name: field.task_listener_field_name, value: field.task_listener_field_value });
			});
		}
		this.editformPanel.getForm().findField('name').setValue(tmp.name);
		this.editformPanel.getForm().findField('value').setValue(Ext.encode(tmp));
	},
	okCallBack: function () {
		var record = this.getRecord();
		if (record) {
			var tmpobj = {
				totalCount: 0
				, items: []
			}
			var tmpfield = {
				totalCount: 0,
				items: []
			}
			var v = this.editformPanel.getForm().findField("value").getValue() || '{}';
			try {
				var obj = eval('(' + v + ')');
			} catch (e) {
				alert('策略值格式错误:' + e);
				return;
			}
			if (!obj.params || !obj.params instanceof Array || !obj.name || !obj.class) {
				alert('策略值格式错误');
				return;
			}

			if (obj.params) {
				obj.params.each(function (param) {
					tmpfield.items.push({
						task_listener_field_name: param.name
						, task_listener_field_value: param.value
					})
				})
				tmpfield.totalCount = obj.params.length;
			}
			var n = this.editformPanel.getForm().findField("name").getValue() || '';
			record.set('task_listener_class', obj.class || '');
			record.set('task_listener_displaytitle', n);
			record.set('task_listener_fields', Ext.encode(tmpfield));
			this.propertywindow.editDirectly('oryx-multiinstance_condition', '');
			record.commit();
			return true;
		}
	}
});
/**
 * userTask通用编辑editor by gk
 */
Ext.UserTaskCommonEditor = Ext.extend(Ext.AbstractCommonEditorPanel, {
	//构造函数
	constructor: function (config, propertywindow) {
		this.propertywindow = propertywindow;
		var initconfig = {};
		this.shapes = this.propertywindow.shapeSelection.shapes;


		this.initStrategyGrid();
		this.initAduRightGrid();

		var listeners = {
			change: function (sender, newValue, oldValue) {
				console.log(this);
				this.propertywindow.update(sender.name, newValue, oldValue);
			}.bind(this)
			,
			keyup: function (textField, e) {
				this.propertywindow.update(textField.name, Ext.getCmp(textField.id).getValue(), textField.value);
			}.bind(this)
			, blur: function () {
			},
		};
		Object.extend(initconfig, config || {});
		Object.extend(initconfig, {
			items: [
				{
					xtype: 'fieldset'
					, title: '通用'
					, columnWidth: 1
					, autoHeight: true
					, items: [
						{
							anchor: "100%"
							, fieldLabel: '名称'
							, xtype: 'textfield'
							, name: 'oryx-name'
							, cls: 'PropertyWindow-CommonEditor-check'
							, labelStyle: 'text-align:right;'
							, enableKeyEvents: true
							, listeners: listeners
							, editable: true
						}, {
							anchor: "100%"
							, fieldLabel: '描述'
							, xtype: 'textarea'
							, name: 'oryx-documentation'
							, cls: 'PropertyWindow-CommonEditor-check'
							, labelStyle: 'text-align:right;'
							, enableKeyEvents: true
							, editable: true
							, listeners: listeners
						}
					]
				}, {
					xtype: 'fieldset'
					, title: '参与人策略设置'
					, columnWidth: 0.5
					, autoHeight: true
					, items: [
						this.StrategyGrid
					]
				}, {
					xtype: 'fieldset'
					, title: '流程审核操作'
					, columnWidth: 1
					, autoHeight: true
					, items: [
						this.aduRightGrid
					]
				}

			]
		});
		Ext.UserTaskCommonEditor.superclass.constructor.call(this, initconfig, this.propertywindow);
	},
	generalGUID: function () {
		return 'zxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
			var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
			return v.toString(16);
		});
	},
	/**
	 * 得到formData对象
	 */
	getFormData: function () {
		var propertywindow = this.propertywindow;
		var proname = 'oryx-formproperties';
		if (propertywindow && propertywindow.shapeSelection.commonPropertiesValues[proname] != undefined) {
			var commonPropertiesValues = propertywindow.shapeSelection.commonPropertiesValues;
			var jsonstr = commonPropertiesValues[proname];
			return eval('(' + (jsonstr || '{}') + ')');
		}
	},
	/**
	 * 设置formdata
	 * @param {} obj 
	 */
	setFormData: function (obj) {
		var propertywindow = this.propertywindow;
		var proname = 'oryx-formproperties';
		var compare = function (obj1, obj2) {
			var val1 = obj1.formproperty_sort;
			var val2 = obj2.formproperty_sort;
			if (val1 < val2) {
				return -1;
			} else if (val1 > val2) {
				return 1;
			} else {
				return 0;
			}
		}
		obj.items.sort(compare);
		obj.totalCount = obj.items.size();
		var ret = Ext.encode(obj);
		propertywindow.editDirectlyByShapes(this.shapes, proname, ret);
	},
	/**
	 * 重设formData
	 */
	reSetFormData: function () {
		var grid = Ext.getCmp('aduRightGrid');
		grid.store.removeAll();
		grid.getStore().commitChanges();

		let defauts = [
			{ formproperty_id: "cantuihu", formproperty_name: "退回", formproperty_value: 0, formproperty_visiable: true, formproperty_alias: "", formproperty_comment: "", formproperty_sort: 3, formproperty_type: 'string', formproperty_required: true }
			, { formproperty_id: "cantij", formproperty_name: "提交/送审", formproperty_value: 1, formproperty_visiable: true, formproperty_alias: "", formproperty_comment: "", formproperty_sort: 0, formproperty_type: 'string', formproperty_required: true }
			, { formproperty_id: "canButy", formproperty_name: "不同意", formproperty_value: 2, formproperty_visiable: true, formproperty_alias: "", formproperty_comment: "", formproperty_sort: 2, formproperty_type: 'string', formproperty_required: true }
			, { formproperty_id: "canTongy", formproperty_name: "同意", formproperty_value: 3, formproperty_visiable: true, formproperty_alias: "", formproperty_comment: "", formproperty_sort: 1, formproperty_type: 'string', formproperty_required: true }
			, { formproperty_id: "canQuh", formproperty_name: "取回", formproperty_value: 4, formproperty_visiable: true, formproperty_alias: "", formproperty_comment: "", formproperty_sort: 4, formproperty_type: 'string', formproperty_required: true }
			, { formproperty_id: "canBufty", formproperty_name: "部分同意", formproperty_value: 7, formproperty_visiable: true, formproperty_alias: "", formproperty_comment: "", formproperty_sort: 5, formproperty_type: 'string', formproperty_required: true }
			, { formproperty_id: "canYuanlth", formproperty_name: "原路退回", formproperty_value: 9, formproperty_visiable: true, formproperty_alias: "", formproperty_comment: "", formproperty_sort: 6, formproperty_type: 'string', formproperty_required: true }
		]
		for (let i = 0; i < defauts.length; i++) {
			var rectype = grid.store.recordType;
			grid.stopEditing();
			this.dsAduRight.add(new rectype(defauts[i]))
		}
		grid.store.commitChanges();

		let obj = this.getFormData();
		obj.items = defauts;
		obj.totalCount = obj.items.length;
		this.setFormData(obj)
	},
	/**
	 * 初始化参与人策略设置grid 
	 */
	initStrategyGrid: function () {
		//记录字段列表
		var RecordType = [
			{ name: 'task_listener_event_type', type: 'string' }
			, { name: 'task_listener_stype', type: 'string' }
			, { name: 'task_listener_displaytitle', type: 'string' }
			, { name: 'task_listener_class', type: 'string' }
			, { name: 'task_listener_expression', type: 'string' }
			, { name: 'task_listener_delegate_expression', type: 'string' }
			, { name: 'task_listener_fields', type: 'string' }
			, { name: 'task_listener_huiq_stype', type: 'string' }
			, { name: 'task_listener_huiqset', type: 'string' }
		]
		let that = this;
		var listners = this.propertywindow.shapeSelection.commonPropertiesValues['oryx-tasklisteners'] || '{}';
		listners = eval('(' + (listners || '{}') + ')');
		var items = listners.items;
		var completecontion = this.propertywindow.shapeSelection.commonPropertiesValues['oryx-multiinstance_condition'];
		var huiq_stype = 'selAll', huiqset = '';
		if (completecontion) {
			huiqset = completecontion.substring(completecontion.lastIndexOf('=') + 1, completecontion.lastIndexOf('}'));
			if (completecontion.indexOf('/') > -1) {
				huiq_stype = 'selRate';
			} else if (completecontion.indexOf('${nrOfCompletedInstances==') > -1) {
				huiq_stype = 'selNum'
			} else {
				huiq_stype = 'selUserdef';
				huiqset = completecontion;
			}
		};
		if (items) {
			items.each(function (item) {
				if (item.task_listener_stype == 'selHuiq') {
					item.task_listener_huiq_stype = huiq_stype;
					item.task_listener_huiqset = huiqset;
				}
			});
		}

		this.dsDataStye = new Ext.data.Store({
			proxy: new Ext.data.MemoryProxy(listners),
			reader: new Ext.data.JsonReader({
				root: 'items',
				totalProperty: 'totalCount'
			}, RecordType)
		});
		this.dsDataStye.load();
		this.dsDataStye.on('update', function (sender, record, operation) {
			ORYX.Log.debug('dsDataStye update [ %0 ]', operation);
			if (operation == 'commit') {
				var writer = new Ext.TasklistenersWriter({}, this.propertywindow, that);
				writer.setValue(record.store);
			}
		}.bind(this));
		this.dsDataStye.on('remove', function (sender, record, index) {
			ORYX.Log.debug('dsDataStye remove [ %0 ]', index);
			var writer = new Ext.TasklistenersWriter({}, this.propertywindow, that);
			writer.setValue(record.store);
		}.bind(this));
		var cmbStore = new Ext.data.SimpleStore({
			fields: [
				{ name: 'value' }
				, { name: 'title' }
			],
			data: [['selPer', '特定人'], ['selTemp', '策略模板'], ['userdef', '自定义'], ['selHuiq', '会签']]
		});
		var guizStore = new Ext.data.SimpleStore({
			fields: [
				{ name: 'value' }
				, { name: 'title' }
			],
			data: [['selAll', '会签全部通过'], ['selNum', '会签通过人数'], ['selRate', '会签通过占比（小数）'], ['selUserdef', '用户自定义']]
		});
		var sStore = new Ext.data.SimpleStore({
			fields: [
				{ name: 'value' }
				, { name: 'title' }
			],
			data: [['com.kdayun.core.workflow.listerner.usertask.DepartPersonListernerImpl', '部门领导']]
		});
		//工具栏
		var toolbar = new Ext.Toolbar(
			[{
				text: ORYX.I18N.PropertyWindow.add,
				icon: getContextPath() + '/static/workflow/editor/images/add.png',
				iconCls: "x-dummy",
				handler: function () {
					var grid = Ext.getCmp('stypGrid');
					var rectype = grid.store.recordType;
					var rec = new rectype({
						task_listener_event_type: 'create',
						task_listener_class: 'Mostly Shade',
						task_listener_fields: '',
						task_listener_stype: '',
						task_listener_displaytitle: ''
					});
					grid.stopEditing();
					grid.store.insert(0, rec);
					grid.startEditing(0, 0);
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.rem,
				icon: getContextPath() + '/static/workflow/editor/images/delete.png',
				iconCls: "x-dummy",
				handler: function () {
					var grid = Ext.getCmp('stypGrid');
					if (grid.getSelectionModel().selection == null) {
						Ext.Msg.alert("提示", "请选中要删除的策略");
						return;
					}
					Ext.MessageBox.confirm('提示', '确实要删除所选的策略吗？', function (btn) {
						if (btn == 'yes') {
							grid.stopEditing();
							var sm = grid.getSelectionModel();
							grid.getStore().remove(sm.selection.record);
							grid.getStore().commitChanges();
						}
					});
				}.bind(this)
			}
			]);
		this.StrategyGrid = new Ext.grid.EditorGridPanel({
			clicksToEdit: 1
			, id: 'stypGrid'
			, stripeRows: true
			, colModel: new Ext.grid.ColumnModel([
				{
					header: '策略类型',
					dataIndex: 'task_listener_stype',
					width: 100,
					sortable: true,
					editor: new Ext.form.ComboBox({
						id: 'cmbtask_listener_stype'
						, mode: 'local'
						, store: cmbStore
						, valueField: 'value'
						, displayField: 'title'
						, editable: false
						, forceSelection: true
						, typeAhead: true
						, triggerAction: 'all'
						, selectOnFocus: true
						, allowBlank: false
						, listeners: {
							select: function (cmb) {
								if (cmb.startValue != cmb.value) {
									var record = this.StrategyGrid.getSelectionModel().selection.record;
									record.data['task_listener_class'] = '';
									record.data['task_listener_fields'] = '';
									record.data['task_listener_displaytitle'] = '';
									record.data['task_listener_huiqset'] = '';
									//record.commit();
									if (cmb.value == 'selHuiq') {
										record.data['task_listener_huiq_stype'] = 'selAll';
										// this.StrategyGrid.getColumnModel().setHidden(3,false);
										// this.StrategyGrid.getColumnModel().setHidden(2,false);
									} else {
										record.data['task_listener_huiq_stype'] = '';
										// this.StrategyGrid.getColumnModel().setHidden(3,true);
										// this.StrategyGrid.getColumnModel().setHidden(2,true);
									}
								}

							}.bind(this)
						}
					}),
					renderer: function (value, cellmeta, record) {
						var index = cmbStore.find(Ext.getCmp('cmbtask_listener_stype').valueField, value);
						var r = cmbStore.getAt(index);
						if (r == null) {
							displayText = value;
						} else {
							displayText = r.data.title;
						}
						return displayText;
					}
				}, {
					header: '处理策略'
					, dataIndex: 'task_listener_displaytitle'
					, mode: 'local'
					, width: 100
					, editor: new Ext.form.StrategysEditor({
						id: 'syedtListener'
						, store: sStore
						, sortable: true
						, valueField: 'value'
						, displayField: 'title'
						, propertywindow: this.propertywindow
					}, this)
				}, {
					header: '会签规则选择',
					dataIndex: 'task_listener_huiq_stype',
					width: 100,
					sortable: true,
					// hidden :true,
					editor: new Ext.form.ComboBox({
						id: 'cmbtask_listener_stype1'
						, mode: 'local'
						, store: guizStore
						, valueField: 'value'
						, displayField: 'title'
						, editable: false
						, forceSelection: true
						, typeAhead: true
						, triggerAction: 'all'
						, selectOnFocus: true
						, allowBlank: true
						, listeners: {
							select: function (cmb) {
								if (cmb.startValue != cmb.value) {
									var record = this.StrategyGrid.getSelectionModel().selection.record;
									record.data['task_listener_huiqset'] = '';
									// if((cmb.value=='selAll'){}{

									// }

									//record.commit();
								}
							}.bind(this)
						}
					}),
					renderer: function (value, cellmeta, record) {
						var index = guizStore.find(Ext.getCmp('cmbtask_listener_stype1').valueField, value);
						var r = guizStore.getAt(index);
						if (r == null) {
							displayText = value;
						} else {
							displayText = r.data.title;
						}
						return displayText;
					}
				},
				{
					header: '会签规则设置'
					, dataIndex: 'task_listener_huiqset'
					, width: 120
					, editor: new Ext.form.TextField({ allowBlank: true, disabled: false })
					// , hidden :true,
				},
			])
			, enableHdMenu: false
			, store: this.dsDataStye
			, tbar: toolbar
			, listeners: {
				beforeedit: function (e) {
					if (e.field == 'task_listener_huiqset' || e.field == 'task_listener_huiq_stype') {
						if (e.record.data.task_listener_stype == 'selHuiq') {
							return true;
						} else {
							return false;
						}
					}
				},
				afterEdit: function (e) {
					if (e.record.data.task_listener_huiq_stype == 'selAll' || !e.record.data.task_listener_huiq_stype || e.field != 'task_listener_huiqset') {
						return;
					};
					var value = e.value;
					if (e.record.data.task_listener_huiq_stype == 'selNum') {
						var re = /^[1-9]+[0-9]*]*$/;
						if (!re.test(value)) {
							Ext.Msg.alert('错误', '错误的数字，请重新输入！');
							e.value = '';
							e.record.set(e.field, '');
							return;
						}
						propertywindow.editDirectly('oryx-multiinstance_condition', '${nrOfCompletedInstances==' + value + '}');
					} else if (e.record.data.task_listener_huiq_stype == 'selRate') {
						var re = /1|0\.[1-9]*$/;
						if (!re.test(value)) {
							Ext.Msg.alert('错误', '错误的数据格式，请重新输入正确的小数！');
							e.record.set(e.field, '');
							return;
						}
						if (value > 1) {
							Ext.Msg.alert('错误', '会签通过占比不能超过1，请重新输入！');
							e.record.set(e.field, '');
							return;
						}
						propertywindow.editDirectly('oryx-multiinstance_condition', '${nrOfCompletedInstances/nrOfInstances>=' + value + '}');
					} else if (e.record.data.task_listener_huiq_stype == 'selUserdef') {
						propertywindow.editDirectly('oryx-multiinstance_condition', value);
					}
				}
			}
		});
	},
	/**
	 * 初始化流程审核操作grid
	 */
	initAduRightGrid: function () {
		var that = this;
		var obj = this.getFormData();
		if (obj) {
			var RecordType = [
				{ name: 'formproperty_id', type: 'string' }
				, { name: 'formproperty_name', type: 'string' }
				, { name: 'formproperty_value', type: 'string' }
				, { name: 'formproperty_visiable', type: 'boolean' }
				, { name: 'formproperty_alias', type: 'string' }
				, { name: 'formproperty_comment', type: 'string' }
				, { name: 'formproperty_sort', type: 'string' }
				, { name: 'formproperty_type', type: 'string' }
				, { name: 'formproperty_required', type: 'string' }
			]
			this.dsAduRight = new Ext.data.Store({
				proxy: new Ext.data.MemoryProxy(obj),
				reader: new Ext.data.JsonReader({
					root: 'items',
					totalProperty: 'totalCount'
				}, RecordType)
			});
			this.dsAduRight.load();
			this.dsAduRight.on('update', function (sender, record, operation) {
				ORYX.Log.debug('dsAduRight update [ %0 ]', operation);
				// if (operation == 'commit') {
				var writer = new Ext.TaskFromDataWriter({}, that.propertywindow, that);
				writer.setValue(record.store);
				// }
			}.bind(this));
			this.dsAduRight.on('remove', function (sender, record, index) {
				ORYX.Log.debug('dsAduRight remove [ %0 ]', index);
				var writer = new Ext.TaskFromDataWriter({}, that.propertywindow, that);
				writer.setValue(record.store);
			}.bind(this));
		}
		//工具栏
		var aduRightGridToolbar = new Ext.Toolbar(
			[{
				text: ORYX.I18N.PropertyWindow.add,
				icon: getContextPath() + '/static/workflow/editor/images/add.png',
				iconCls: "x-dummy",
				handler: function () {
					var grid = Ext.getCmp('aduRightGrid');
					var rectype = grid.store.recordType;
					var r = {
						formproperty_id: this.generalGUID(),
						formproperty_name: '审核操作',
						formproperty_value: 99,
						formproperty_visiable: true,
						formproperty_alias: '',
						formproperty_comment: '',
						formproperty_sort: 0,
						formproperty_type: 'string',
						formproperty_required: true
					}
					var rec = new rectype(r);
					grid.stopEditing();
					grid.store.insert(0, rec);
					grid.startEditing(0, 0);
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.rem,
				icon: getContextPath() + '/static/workflow/editor/images/delete.png',
				iconCls: "x-dummy",
				handler: function () {
					var grid = Ext.getCmp('aduRightGrid');
					if (grid.getSelectionModel().selection == null) {
						Ext.Msg.alert("提示", "请选中要删除流程审核操作");
						return;
					}
					Ext.MessageBox.confirm('提示', '确实要删除选择的流程审核操作吗？', function (btn) {
						if (btn == 'yes') {
							grid.stopEditing();
							var sm = grid.getSelectionModel();
							grid.getStore().remove(sm.selection.record);
							grid.getStore().commitChanges();
						}
					});
				}.bind(this)
			}, {
				text: '重设',
				icon: getContextPath() + '/static/workflow/editor/images/wrench_orange.png',
				iconCls: "x-dummy",
				handler: function () {
					Ext.MessageBox.confirm('提示', '是否要重设所有流程审核操作?这样会覆盖原审核操作.按是重设审核操作', function (btn) {
						if (btn == 'yes') {
							that.reSetFormData();
						}
					});
				}.bind(this)
			}
			]);
		this.aduRightGrid = new Ext.grid.EditorGridPanel({
			clicksToEdit: 1
			, id: 'aduRightGrid'
			, stripeRows: true
			, width: 'auto'
			, autoHeight: 100
			, tbar: aduRightGridToolbar
			, colModel: new Ext.grid.ColumnModel([
				{
					header: '审核名称',
					dataIndex: 'formproperty_name',
					width: 90,
					editor: new Ext.form.TextField({ allowBlank: false, disabled: false })
				},
				{
					header: '值',
					dataIndex: 'formproperty_value',
					width: 40,
					editor: new Ext.form.TextField({ allowBlank: false, disabled: false })
				},
				{
					header: '可见',
					dataIndex: 'formproperty_visiable',
					width: 40,
					editor: new Ext.form.CheckBoxField({ name: "visiable", allowBlank: false, disabled: false })
				},
				{
					header: '自定义名称',
					dataIndex: 'formproperty_alias',
					width: 80,
					editor: new Ext.form.TextField({ allowBlank: true, disabled: false })
				}, {
					header: '默认意见'
					, dataIndex: 'formproperty_comment'
					, width: 70
					, editor: new Ext.form.TextField({ allowBlank: true, disabled: false })
				}, {
					header: '排序'
					, dataIndex: 'formproperty_sort'
					, width: 40
					, editor: new Ext.form.TextField({ allowBlank: true, disabled: false })
				}
			])
			, enableHdMenu: false
			, store: this.dsAduRight
			, listeners: {
				afterEdit: function (e) {
					var isRepeat = function (arr) {
						var hash = {};
						for (var i = 0; i < arr.length; i++) {
							if (hash[arr[i]['formproperty_value']]) {
								return arr[i]['formproperty_value'];
							}
							hash[arr[i]['formproperty_value']] = true;
						}
						return false;
					}
					var obj = that.getFormData();
					if (obj) {
						if (obj.items) {
							obj.items.each(function (item) {
								if (item['formproperty_id'] == e.record.data.formproperty_id) {
									var name = item['formproperty_name'];
									if (name.indexOf('-') > -1) {
										name = name.substring(0, name.indexOf('-'));
									}
									item[e.field] = e.value;
									return;
								}
							});
						}
						var isr = isRepeat(obj.items);
						if (isr !== false) {
							Ext.Msg.alert('保存失败', '不允许存在重复的审核操作值[' + isr + ']')
							return;
						}
					}

				}
			}
		})

		if (obj) {
			var ret = obj.items;
			if (ret == undefined || (obj && obj.items.length == 0)) {
				that.reSetFormData();
			}
		}
	}

});

/**
 * 流程图通用编辑editor by gk
 */
Ext.WorkFlowCommonEditor = Ext.extend(Ext.AbstractCommonEditorPanel, {
	constructor: function (config, propertywindow) {
		this.dataSource = new Ext.data.GroupingStore();
		this.propertywindow = propertywindow;
		var initconfig = {};
		var listeners = {
			change: function (sender, newValue, oldValue) {
				this.propertywindow.update(sender.name, newValue, oldValue);
			}.bind(this)
			, keyup: function () {
				alert(keyup);
			}.bind(this)
		};

		Object.extend(initconfig, config || {});
		Object.extend(initconfig, {
			items: [
				{
					xtype: 'fieldset'
					, title: '通用'
					, columnWidth: 1
					, autoHeight: true
					, items: [
						{
							fieldLabel: '名称'
							, anchor: "100%"
							, xtype: 'textfield'
							, name: 'oryx-name'
							, cls: 'PropertyWindow-CommonEditor-check'
							, labelStyle: 'text-align:right;'
							, listeners: listeners
						}, {
							fieldLabel: '描述'
							, anchor: "100%"
							, xtype: 'textarea'
							, name: 'oryx-documentation'
							, cls: 'PropertyWindow-CommonEditor-check'
							, labelStyle: 'text-align:right;'
							, listeners: listeners
						}
						// , {
						// 	fieldLabel: '关联功能'
						// 	, anchor: "100%"
						// 	, xtype: 'functionfield'
						// 	, cls: 'PropertyWindow-CommonEditor-check'
						// 	, labelStyle: 'text-align:right;'
						// 	, dtype: 'String'
						// 	, name: 'oryx-dataproperties.functionId'
						// 	, treeId: 'RWID'
						// 	, treeName: 'OBJNAME'
						// 	, treePid: 'SYS_PARENTID'
						// 	, dataUrl: getContextPath() + '/manager/corefunction/query'
						// 	, listeners: listeners
						// 	, propertywindow: propertywindow

						// }
					]
				}
			]
		});
		Ext.WorkFlowCommonEditor.superclass.constructor.call(this, initconfig, this.propertywindow);
	},
});

/**
 * 线条的节点editor by gk
 */
Ext.LineCommonEditor = Ext.extend(Ext.AbstractCommonEditorPanel, {
	//构造函数
	constructor: function (config, propertywindow) {
		this.dataSource = new Ext.data.GroupingStore();
		this.propertywindow = propertywindow;
		var self = this;
		var initconfig = {};
		var listeners = {
			change: function (sender, newValue, oldValue) {
				this.propertywindow.update(sender.name, newValue, oldValue);
			}.bind(this)
			, keyup: function () {
				alert(keyup);
			}.bind(this)
		};
		Object.extend(initconfig, config || {});
		Object.extend(initconfig, {
			bodyStyle: 'padding:10px'
			, items: [{
				anchor: "100%"
				, fieldLabel: '名称'
				, xtype: 'textfield'
				, name: 'oryx-name'
				, labelStyle: 'text-align:right;'
				, listeners: listeners
			},
			{
				anchor: "100%"
				, fieldLabel: '描述'
				, xtype: 'textarea'
				, name: 'oryx-documentation'
				, labelStyle: 'text-align:right;'
				, listeners: listeners
			}, {
				fieldLabel: '条件'
				, anchor: "100%"
				, height: 500
				, xtype: 'scriptfield'
				, id: 'oryx-conditionsequenceflow'
				, name: 'oryx-conditionsequenceflow'
				, labelStyle: 'text-align:right;'
				, language: 'java'
				, wrap: 'free'
				, listeners: listeners
				, propertywindow: propertywindow
			}
			]
		});
		Ext.LineCommonEditor.superclass.constructor.call(this, initconfig, this.propertywindow);
	}
});

/**
 * ServerTask的节点editor by gk
 */
Ext.ServerTaskCommonEditor = Ext.extend(Ext.AbstractCommonEditorPanel, {
	//构造函数
	constructor: function (config, propertywindow) {
		this.dataSource = new Ext.data.GroupingStore();
		this.propertywindow = propertywindow;
		var self = this;
		var initconfig = {};
		var listeners = {
			change: function (sender, newValue, oldValue) {
				this.propertywindow.update(sender.name, newValue, oldValue);
			}.bind(this)
			, keyup: function () {
				alert(keyup);
			}.bind(this)
		};
		var fieldBlankTxt = '格式如下:{'
			+ '	\'totalCount\': 1,'
			+ '	\'items\': ['
			+ '		{'
			+ '			\'servicetask_field_name\': \'122\','
			+ '			\'servicetask_field_value\': \'2222\','
			+ '			\'servicetask_field_expression\': \'\''
			+ '		}'
			+ '	]'
			+ '}';
		Object.extend(initconfig, config || {});
		Object.extend(initconfig, {
			items: [{
				xtype: 'fieldset'
				, title: '通用'
				, autoHeight: true
				, items: [
					{
						fieldLabel: '名称'
						, anchor: "100%"
						, xtype: 'textfield'
						, name: 'oryx-name'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '描述'
						, anchor: "100%"
						, xtype: 'textarea'
						, name: 'oryx-documentation'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '类名'
						, anchor: "100%"
						, xtype: 'textarea'
						, placeholder: '输入策略的类名'
						, name: 'oryx-servicetaskclass'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '属性'
						, anchor: "100%"
						, height: 500
						, xtype: 'scriptfield'
						, name: 'oryx-servicetaskfields'
						, labelStyle: 'text-align:right;'
						, language: 'json'
						, listeners: listeners
						, propertywindow: propertywindow
					}
				]
			}]
		});
		Ext.DefaultCommonEditor.superclass.constructor.call(this, initconfig, this.propertywindow);
	}
});
/**
 * ScriptTask的节点editor by gk
 */
Ext.ScriptTaskCommonEditor = Ext.extend(Ext.AbstractCommonEditorPanel, {
	//构造函数
	constructor: function (config, propertywindow) {
		this.dataSource = new Ext.data.GroupingStore();
		this.propertywindow = propertywindow;
		var self = this;
		var initconfig = {};
		var listeners = {
			change: function (sender, newValue, oldValue) {
				this.propertywindow.update(sender.name, newValue, oldValue);
				if (sender.name == 'oryx-scriptformat') {
					var cmp = Ext.getCmp('oryx-scripttext');
					if (cmp) {
						cmp.setLanguage(newValue);
					}
				}
			}.bind(this)
			, keyup: function () {
				alert(keyup);
			}.bind(this)
		};
		//选择框的数据源store
		var store = new Ext.data.SimpleStore({
			fields: [
				{ name: 'value' }
				, { name: 'title' }
			]
			, data: [
				['groovy', 'Groovy']
			]
		});
		Object.extend(initconfig, config || {});
		Object.extend(initconfig, {
			items: [{
				xtype: 'fieldset'
				, title: '通用'
				, autoHeight: true
				, items: [
					{
						fieldLabel: '名称'
						, anchor: "100%"
						, xtype: 'textfield'
						, name: 'oryx-name'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '描述'
						, anchor: "100%"
						, xtype: 'textarea'
						, name: 'oryx-documentation'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '脚本类型'
						, anchor: "100%"
						, xtype: 'combo'
						, store: store
						, displayField: 'title'
						, valueField: 'value'
						, mode: 'local'
						, triggerAction: 'all'
						, name: 'oryx-scriptformat'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '脚本'
						, anchor: "100%"
						, height: 500
						, xtype: 'scriptfield'
						, id: 'oryx-scripttext'
						, name: 'oryx-scripttext'
						, labelStyle: 'text-align:right;'
						, language: 'groovy'
						, listeners: listeners
						, propertywindow: propertywindow
					}
				]
			}]
		});
		Ext.DefaultCommonEditor.superclass.constructor.call(this, initconfig, this.propertywindow);
	}
});

Ext.MailTaskCommonEditor = Ext.extend(Ext.AbstractCommonEditorPanel, {
	//构造函数
	constructor: function (config, propertywindow) {
		this.dataSource = new Ext.data.GroupingStore();
		this.propertywindow = propertywindow;
		var self = this;
		var initconfig = {};
		var listeners = {
			change: function (sender, newValue, oldValue) {
				this.propertywindow.update(sender.name, newValue, oldValue);
				if (sender.name == 'oryx-scriptformat') {
					var cmp = Ext.getCmp('oryx-scripttext');
					if (cmp) {
						cmp.setLanguage(newValue);
					}
				}
			}.bind(this)
			, keyup: function () {
				alert(keyup);
			}.bind(this)
		};
		//选择框的数据源store
		var store = new Ext.data.SimpleStore({
			fields: [
				{ name: 'value' }
				, { name: 'title' }
			]
			, data: [
				['groovy', 'Groovy']
			]
		});
		Object.extend(initconfig, config || {});
		Object.extend(initconfig, {
			items: [{
				xtype: 'fieldset'
				, title: '通用'
				, autoHeight: true
				, items: [
					{
						fieldLabel: '名称'
						, anchor: "100%"
						, xtype: 'textfield'
						, name: 'oryx-name'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '描述'
						, anchor: "100%"
						, xtype: 'textarea'
						, name: 'oryx-documentation'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '邮件接收人'
						, anchor: "100%"
						, xtype: 'textarea'
						, name: 'oryx-mailtaskto'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '邮件发送人'
						, anchor: "100%"
						, xtype: 'textfield'
						, name: 'oryx-mailtaskfrom'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '邮件主题'
						, anchor: "100%"
						, xtype: 'textarea'
						, name: 'oryx-mailtasksubject'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '抄送'
						, anchor: "100%"
						, xtype: 'textarea'
						, name: 'oryx-mailtaskcc'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '密件抄送'
						, anchor: "100%"
						, xtype: 'textarea'
						, name: 'oryx-mailtaskbcc'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '字符集(编码格式)'
						, anchor: "100%"
						, xtype: 'textfield'
						, name: 'oryx-mailtaskcharset'
						, labelStyle: 'text-align:right;'
						, placeholder: 'utf8'
						, listeners: listeners
					}, {
						fieldLabel: '邮件Html'
						, anchor: "100%"
						, height: 500
						, xtype: 'scriptfield'
						, id: 'oryx-mailtaskhtml'
						, name: 'oryx-mailtaskhtml'
						, labelStyle: 'text-align:right;'
						, language: 'html'
						, listeners: listeners
						, propertywindow: propertywindow
					}
				]
			}]
		});
		Ext.DefaultCommonEditor.superclass.constructor.call(this, initconfig, this.propertywindow);
	}
});
/**
 * 默认的节点editor by gk
 */
Ext.DefaultCommonEditor = Ext.extend(Ext.AbstractCommonEditorPanel, {
	//构造函数
	constructor: function (config, propertywindow) {
		this.dataSource = new Ext.data.GroupingStore();
		this.propertywindow = propertywindow;
		var self = this;
		var initconfig = {};
		var listeners = {
			change: function (sender, newValue, oldValue) {
				this.propertywindow.update(sender.name, newValue, oldValue);
			}.bind(this)
			, keyup: function () {
				alert(keyup);
			}.bind(this)
		};
		Object.extend(initconfig, config || {});
		Object.extend(initconfig, {
			items: [{
				xtype: 'fieldset'
				, title: '通用'
				, columnWidth: 1
				, autoHeight: true
				, items: [
					{
						fieldLabel: '名称'
						, anchor: "100%"
						, xtype: 'textfield'
						, name: 'oryx-name'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}, {
						fieldLabel: '描述'
						, anchor: "100%"
						, xtype: 'textarea'
						, name: 'oryx-documentation'
						, labelStyle: 'text-align:right;'
						, listeners: listeners
					}
				]
			}]
		});
		Ext.DefaultCommonEditor.superclass.constructor.call(this, initconfig, this.propertywindow);
	}
});

/**
弹出的选择框Field by gk
**/
Ext.form.ComplexSelectValueDlgField = Ext.extend(Ext.form.TriggerField, {
    /**
     * If the trigger was clicked a dialog has to be opened
     * to enter the values for the complex property.
     */
	onTriggerClick: function () {

		if (this.disabled) {
			return;
		}

		var grid = new Ext.form.TextArea({
			anchor: '100% 100%',
			value: this.value,
			listeners: {
				focus: function () {
					this.facade.disableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
				}.bind(this)
			}
		})


		// Basic Dialog
		var dialog = new Ext.Window({
			layout: 'anchor',
			autoCreate: true,
			title: ORYX.I18N.PropertyWindow.text,
			height: 500,
			width: 500,
			modal: true,
			collapsible: false,
			fixedcenter: true,
			shadow: true,
			proxyDrag: true,
			keys: [{
				key: 27,
				fn: function () {
					dialog.hide()
				}.bind(this)
			}],
			items: [grid],
			listeners: {
				hide: function () {
					this.fireEvent('dialogClosed', this.value);
					//this.focus.defer(10, this);
					dialog.destroy();
				}.bind(this)
			},
			buttons: [{
				text: ORYX.I18N.PropertyWindow.ok,
				handler: function () {
					// store dialog input
					var value = grid.getValue();
					this.setValue(value);

					this.dataSource.getAt(this.row).set('value', value)
					this.dataSource.commitChanges()

					dialog.hide()
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.cancel,
				handler: function () {
					this.setValue(this.value);
					dialog.hide()
				}.bind(this)
			}]
		});

		dialog.show();
		grid.render();

		this.grid.stopEditing();
		grid.focus(false, 100);

	}
});

Ext.form.ComplexModelLinkField = Ext.extend(Ext.form.TriggerField, {
	onTriggerClick: function () {
		if (this.disabled) {
			return;
		}

		var CallElementDef = Ext.data.Record.create([{
			name: 'name'
		}, {
			name: 'revision'
		}, {
			name: 'imgsrc'
		}]);

		var calldefsProxy = new Ext.data.MemoryProxy({
			root: []
		});

		var calldefs = new Ext.data.Store({
			autoDestroy: true,
			reader: new Ext.data.JsonReader({
				root: "root"
			}, CallElementDef),
			proxy: calldefsProxy,
			sorters: [{
				property: 'name',
				direction: 'ASC'
			}]
		});
		calldefs.load();

		var loadProcessesMask = new Ext.LoadMask(Ext.getBody(), { msg: 'Loading Process Information' });
		loadProcessesMask.show();
		Ext.Ajax.request({
			url: ORYX.CONFIG.MODEL_LIST_URL,
			method: 'GET',
			success: function (response) {
				try {
					loadProcessesMask.hide();
					if (response.responseText.length > 0 && response.responseText != "false") {
						var responseJson = Ext.decode(response.responseText);
						var modelsArray = responseJson["models"];
						for (var i = 0; i < modelsArray.length; i++) {
							var modelObj = modelsArray[i];
							calldefs.add(new CallElementDef({
								name: modelObj["name"],
								revision: modelObj["revision"],
								imgsrc: ORYX.CONFIG.SERVER_MODEL_HANDLER + "/" + modelObj["modelId"] + "/svg"
							}));
						}

						calldefs.commitChanges();

						var gridId = Ext.id();
						var grid = new Ext.grid.EditorGridPanel({
							store: calldefs,
							id: gridId,
							stripeRows: true,
							cm: new Ext.grid.ColumnModel([new Ext.grid.RowNumberer(), {
								id: 'pid',
								header: 'Process Id',
								width: 200,
								dataIndex: 'name',
								editor: new Ext.form.TextField({ allowBlank: false, disabled: true })
							}, {
								id: 'revi',
								header: 'Revision',
								width: 200,
								dataIndex: 'revision',
								editor: new Ext.form.TextField({ allowBlank: false, disabled: true })
							}, {
								id: 'pim',
								header: 'Process Image',
								width: 250,
								dataIndex: 'imgsrc',
								renderer: function (val) {
									if (val && val.length > 0) {
										return '<center><img src="' + ORYX.CONFIG.ROOT_PATH + '/images/processimagepreview.png" onclick="new ImageViewer({title: \'Process Image\', width: \'1100\', height: \'900\', autoScroll: true, fixedcenter: true, src: \'' + val + '\',hideAction: \'close\'}).show();" alt="Click to view Process Image"/></center>';
									} else {
										return "<center>Process image not available.</center>";
									}
								}
							}]),
							autoHeight: true
						});

						grid.on('afterrender', function (e) {
							if (this.value.length > 0) {
								var index = 0;
								var val = this.value;
								var mygrid = grid;
								calldefs.data.each(function () {
									if (this.data['name'] == val) {
										mygrid.getSelectionModel().select(index, 1);
									}
									index++;
								});
							}
						}.bind(this));

						var calledElementsPanel = new Ext.Panel({
							id: 'calledElementsPanel',
							title: '<center>Select Process Id and click "Save" to select.</center>',
							layout: 'column',
							items: [
								grid
							],
							layoutConfig: {
								columns: 1
							},
							defaults: {
								columnWidth: 1.0
							}
						});

						var dialog = new Ext.Window({
							layout: 'anchor',
							autoCreate: true,
							title: 'Editor for Called Elements',
							height: 350,
							width: 680,
							modal: true,
							collapsible: false,
							fixedcenter: true,
							shadow: true,
							resizable: true,
							proxyDrag: true,
							autoScroll: true,
							keys: [{
								key: 27,
								fn: function () {
									dialog.hide()
								}.bind(this)
							}],
							items: [calledElementsPanel],
							listeners: {
								hide: function () {
									this.fireEvent('dialogClosed', this.value);
									dialog.destroy();
								}.bind(this)
							},
							buttons: [{
								text: 'Save',
								handler: function () {
									if (grid.getSelectionModel().getSelectedCell() != null) {
										var selectedIndex = grid.getSelectionModel().getSelectedCell()[0];
										var outValue = calldefs.getAt(selectedIndex).data['name'];
										grid.stopEditing();
										grid.getView().refresh();
										this.setValue(outValue);
										this.dataSource.getAt(this.row).set('value', outValue)
										this.dataSource.commitChanges()
										dialog.hide()
									} else {
										Ext.Msg.alert('Please select a process id.');
									}
								}.bind(this)
							}, {
								text: ORYX.I18N.PropertyWindow.cancel,
								handler: function () {
									this.setValue(this.value);
									dialog.hide()
								}.bind(this)
							}]
						});

						dialog.show();
						grid.render();
						grid.fireEvent('afterrender');
						this.grid.stopEditing();
						grid.focus(false, 100);
					} else {
						Ext.Msg.alert('Unable to find other processes in pacakge.');
					}
				} catch (e) {
					Ext.Msg.alert('Error resolving other process info :\n' + e);
				}
			}.bind(this),
			failure: function () {
				loadProcessesMask.hide();
				Ext.Msg.alert('Error resolving other process info.');
			}
		});
	}
});
/**
 * 流程定义field
 */
Ext.form.ListenerDefinitionField = Ext.extend(Ext.form.TriggerField, {
	onTriggerClick: function () {
		if (this.disabled) {
			return;
		}

		var ListenerDef = Ext.data.Record.create([{
			name: 'class'
		}, {
			name: 'expression'
		}, {
			name: 'event'
		}]);

		var calldefsProxy = new Ext.data.MemoryProxy({
			root: []
		});

		var calldefs = new Ext.data.Store({
			autoDestroy: true,
			reader: new Ext.data.JsonReader({
				root: "root"
			}, ListenerDef),
			proxy: calldefsProxy,
			sorters: [{
				property: 'event',
				direction: 'ASC'
			}]
		});
		calldefs.load();

		var gridId = Ext.id();
		var grid = new Ext.grid.EditorGridPanel({
			store: calldefs,
			id: gridId,
			stripeRows: true,
			cm: new Ext.grid.ColumnModel([new Ext.grid.RowNumberer(), {
				id: 'pid',
				header: 'Process Id',
				width: 200,
				dataIndex: 'class',
				editor: new Ext.form.TextField({ allowBlank: false, disabled: true })
			}, {
				id: 'revi',
				header: 'Revision',
				width: 200,
				dataIndex: 'expression',
				editor: new Ext.form.TextField({ allowBlank: false, disabled: true })
			}, {
				id: 'pim',
				header: 'Process Image',
				width: 250,
				dataIndex: 'event',
				renderer: function (val) {
					if (val && val.length > 0) {
						return '<center><img src="' + ORYX.CONFIG.ROOT_PATH + '/images/processimagepreview.png" onclick="new ImageViewer({title: \'Process Image\', width: \'1100\', height: \'900\', autoScroll: true, fixedcenter: true, src: \'' + val + '\',hideAction: \'close\'}).show();" alt="Click to view Process Image"/></center>';
					} else {
						return "<center>Process image not available.</center>";
					}
				}
			}]),
			autoHeight: true
		});

		var calledElementsPanel = new Ext.Panel({
			id: 'calledElementsPanel',
			title: '<center>Select Process Id and click "Save" to select.</center>',
			layout: 'column',
			items: [
				grid
			],
			layoutConfig: {
				columns: 1
			},
			defaults: {
				columnWidth: 1.0
			}
		});

		var dialog = new Ext.Window({
			layout: 'anchor',
			autoCreate: true,
			title: 'Editor for Called Elements',
			height: 350,
			width: 680,
			modal: true,
			collapsible: false,
			fixedcenter: true,
			shadow: true,
			resizable: true,
			proxyDrag: true,
			autoScroll: true,
			keys: [{
				key: 27,
				fn: function () {
					dialog.hide()
				}.bind(this)
			}],
			items: [calledElementsPanel],
			listeners: {
				hide: function () {
					this.fireEvent('dialogClosed', this.value);
					dialog.destroy();
				}.bind(this)
			},
			buttons: [{
				text: 'Save',
				handler: function () {
					if (grid.getSelectionModel().getSelectedCell() != null) {
						var selectedIndex = grid.getSelectionModel().getSelectedCell()[0];
						var outValue = calldefs.getAt(selectedIndex).data['name'];
						grid.stopEditing();
						grid.getView().refresh();
						this.setValue(outValue);
						this.dataSource.getAt(this.row).set('value', outValue)
						this.dataSource.commitChanges()
						dialog.hide()
					} else {
						Ext.Msg.alert('Please select a process id.');
					}
				}.bind(this)
			}, {
				text: ORYX.I18N.PropertyWindow.cancel,
				handler: function () {
					this.setValue(this.value);
					dialog.hide()
				}.bind(this)
			}]
		});

		dialog.show();
		grid.render();
		grid.fireEvent('afterrender');
		this.grid.stopEditing();
		grid.focus(false, 100);
	}
});


/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
if (!ORYX.Plugins) {
	ORYX.Plugins = new Object();
}

/**
 * This plugin is responsible for displaying loading indicators and to prevent
 * the user from accidently unloading the page by, e.g., pressing the backspace
 * button and returning to the previous site in history.
 * @param {Object} facade The editor plugin facade to register enhancements with.
 */
ORYX.Plugins.Loading = {

	construct: function (facade) {

		this.facade = facade;

		// The parent Node
		this.node = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", this.facade.getCanvas().getHTMLContainer().parentNode, ['div', {
			'class': 'LoadingIndicator'
		}, '']);

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_LOADING_ENABLE, this.enableLoading.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_LOADING_DISABLE, this.disableLoading.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_LOADING_STATUS, this.showStatus.bind(this));

		this.disableLoading();
	},

	enableLoading: function (options) {
		if (options.text)
			this.node.innerHTML = options.text + "...";
		else
			this.node.innerHTML = ORYX.I18N.Loading.waiting;
		this.node.removeClassName('StatusIndicator');
		this.node.addClassName('LoadingIndicator');
		this.node.style.display = "block";

		var pos = this.facade.getCanvas().rootNode.parentNode.parentNode.parentNode.parentNode;

		this.node.style.top = pos.offsetTop + 'px';
		this.node.style.left = pos.offsetLeft + 'px';

	},

	disableLoading: function () {
		this.node.style.display = "none";
	},

	showStatus: function (options) {
		if (options.text) {
			this.node.innerHTML = options.text;
			this.node.addClassName('StatusIndicator');
			this.node.removeClassName('LoadingIndicator');
			this.node.style.display = 'block';

			var pos = this.facade.getCanvas().rootNode.parentNode.parentNode.parentNode.parentNode;

			this.node.style.top = pos.offsetTop + 'px';
			this.node.style.left = pos.offsetLeft + 'px';

			var tout = options.timeout ? options.timeout : 2000;

			window.setTimeout((function () {

				this.disableLoading();

			}).bind(this), tout);
		}

	}
}

ORYX.Plugins.Loading = Clazz.extend(ORYX.Plugins.Loading);
/**
 * Copyright (c) 2008
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
if (!ORYX.Plugins) {
	ORYX.Plugins = new Object();
}

/**
 * This plugin is responsible for resizing the canvas.
 * @param {Object} facade The editor plugin facade to register enhancements with.
 */
ORYX.Plugins.CanvasResize = Clazz.extend({

	construct: function (facade) {

		this.facade = facade;

		new ORYX.Plugins.CanvasResizeButton(this.facade.getCanvas(), "N", this.resize.bind(this));
		new ORYX.Plugins.CanvasResizeButton(this.facade.getCanvas(), "W", this.resize.bind(this));
		new ORYX.Plugins.CanvasResizeButton(this.facade.getCanvas(), "E", this.resize.bind(this));
		new ORYX.Plugins.CanvasResizeButton(this.facade.getCanvas(), "S", this.resize.bind(this));

	},

	resize: function (position, shrink) {

		resizeCanvas = function (position, extentionSize, facade) {
			var canvas = facade.getCanvas();
			var b = canvas.bounds;
			var scrollNode = facade.getCanvas().getHTMLContainer().parentNode.parentNode;

			if (position == "E" || position == "W") {
				canvas.setSize({ width: (b.width() + extentionSize) * canvas.zoomLevel, height: (b.height()) * canvas.zoomLevel })

			} else if (position == "S" || position == "N") {
				canvas.setSize({ width: (b.width()) * canvas.zoomLevel, height: (b.height() + extentionSize) * canvas.zoomLevel })
			}

			if (position == "N" || position == "W") {

				var move = position == "N" ? { x: 0, y: extentionSize } : { x: extentionSize, y: 0 };

				// Move all children
				canvas.getChildNodes(false, function (shape) { shape.bounds.moveBy(move) })
				// Move all dockers, when the edge has at least one docked shape
				var edges = canvas.getChildEdges().findAll(function (edge) { return edge.getAllDockedShapes().length > 0 })
				var dockers = edges.collect(function (edge) { return edge.dockers.findAll(function (docker) { return !docker.getDockedShape() }) }).flatten();
				dockers.each(function (docker) { docker.bounds.moveBy(move) })
			} else if (position == "S") {
				scrollNode.scrollTop += extentionSize;
			} else if (position == "E") {
				scrollNode.scrollLeft += extentionSize;
			}

			canvas.update();
			facade.updateSelection();
		}

		var commandClass = ORYX.Core.Command.extend({
			construct: function (position, extentionSize, facade) {
				this.position = position;
				this.extentionSize = extentionSize;
				this.facade = facade;
			},
			execute: function () {
				resizeCanvas(this.position, this.extentionSize, this.facade);
			},
			rollback: function () {
				resizeCanvas(this.position, -this.extentionSize, this.facade);
			},
			update: function () {
			}
		});

		var extentionSize = ORYX.CONFIG.CANVAS_RESIZE_INTERVAL;
		if (shrink) extentionSize = -extentionSize;
		var command = new commandClass(position, extentionSize, this.facade);

		this.facade.executeCommands([command]);

	}

});


ORYX.Plugins.CanvasResizeButton = Clazz.extend({

	construct: function (canvas, position, callback) {
		this.canvas = canvas;
		var parentNode = canvas.getHTMLContainer().parentNode.parentNode.parentNode;

		window.myParent = parentNode
		var scrollNode = parentNode.firstChild;
		var svgRootNode = scrollNode.firstChild.firstChild;
		// The buttons
		var buttonGrow = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", parentNode, ['div', { 'class': 'canvas_resize_indicator canvas_resize_indicator_grow' + ' ' + position, 'title': ORYX.I18N.RESIZE.tipGrow + ORYX.I18N.RESIZE[position] }]);
		var buttonShrink = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", parentNode, ['div', { 'class': 'canvas_resize_indicator canvas_resize_indicator_shrink' + ' ' + position, 'title': ORYX.I18N.RESIZE.tipShrink + ORYX.I18N.RESIZE[position] }]);

		// Defines a callback which gives back
		// a boolean if the current mouse event 
		// is over the particular button area
		var offSetWidth = 60;
		var isOverOffset = function (event) {
			if (event.target != parentNode && event.target != scrollNode && event.target != scrollNode.firstChild && event.target != svgRootNode && event.target != scrollNode) { return false }
			//if(inCanvas){offSetWidth=30}else{offSetWidth=30*2}
			//Safari work around
			var X = event.layerX !== undefined ? event.layerX : event.offsetX;
			var Y = event.layerY !== undefined ? event.layerY : event.offsetY;

			if ((X - scrollNode.scrollLeft) < 0 || Ext.isSafari) { X += scrollNode.scrollLeft; }
			if ((Y - scrollNode.scrollTop) < 0 || Ext.isSafari) { Y += scrollNode.scrollTop; }

			//

			if (position == "N") {
				return Y < offSetWidth + scrollNode.firstChild.offsetTop;
			} else if (position == "W") {
				return X < offSetWidth + scrollNode.firstChild.offsetLeft;
			} else if (position == "E") {
				//other offset
				var offsetRight = (scrollNode.offsetWidth - (scrollNode.firstChild.offsetLeft + scrollNode.firstChild.offsetWidth));
				if (offsetRight < 0) offsetRight = 0;
				return X > scrollNode.scrollWidth - offsetRight - offSetWidth;
			} else if (position == "S") {
				//other offset
				var offsetDown = (scrollNode.offsetHeight - (scrollNode.firstChild.offsetTop + scrollNode.firstChild.offsetHeight));
				if (offsetDown < 0) offsetDown = 0;

				return Y > scrollNode.scrollHeight - offsetDown - offSetWidth;
			}

			return false;
		}

		var showButtons = (function () {
			buttonGrow.show();

			var x1, y1, x2, y2;
			try {
				var bb = this.canvas.getRootNode().childNodes[1].getBBox();
				x1 = bb.x;
				y1 = bb.y;
				x2 = bb.x + bb.width;
				y2 = bb.y + bb.height;
			} catch (e) {
				this.canvas.getChildShapes(true).each(function (shape) {
					var absBounds = shape.absoluteBounds();
					var ul = absBounds.upperLeft();
					var lr = absBounds.lowerRight();
					if (x1 == undefined) {
						x1 = ul.x;
						y1 = ul.y;
						x2 = lr.x;
						y2 = lr.y;
					} else {
						x1 = Math.min(x1, ul.x);
						y1 = Math.min(y1, ul.y);
						x2 = Math.max(x2, lr.x);
						y2 = Math.max(y2, lr.y);
					}
				});
			}

			var w = canvas.bounds.width();
			var h = canvas.bounds.height();

			var isEmpty = canvas.getChildNodes().size() == 0;

			if (position == "N" && (y1 > ORYX.CONFIG.CANVAS_RESIZE_INTERVAL || (isEmpty && h > ORYX.CONFIG.CANVAS_RESIZE_INTERVAL))) buttonShrink.show();
			else if (position == "E" && (w - x2) > ORYX.CONFIG.CANVAS_RESIZE_INTERVAL) buttonShrink.show();
			else if (position == "S" && (h - y2) > ORYX.CONFIG.CANVAS_RESIZE_INTERVAL) buttonShrink.show();
			else if (position == "W" && (x1 > ORYX.CONFIG.CANVAS_RESIZE_INTERVAL || (isEmpty && w > ORYX.CONFIG.CANVAS_RESIZE_INTERVAL))) buttonShrink.show();
			else buttonShrink.hide();
		}).bind(this);

		var hideButtons = function () {
			buttonGrow.hide();
			buttonShrink.hide();
		}

		// If the mouse move is over the button area, show the button
		scrollNode.addEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, function (event) { if (isOverOffset(event)) { showButtons(); } else { hideButtons() } }, false);
		// If the mouse is over the button, show them
		buttonGrow.addEventListener(ORYX.CONFIG.EVENT_MOUSEOVER, function (event) { showButtons(); }, true);
		buttonShrink.addEventListener(ORYX.CONFIG.EVENT_MOUSEOVER, function (event) { showButtons(); }, true);
		// If the mouse is out, hide the button
		//scrollNode.addEventListener(		ORYX.CONFIG.EVENT_MOUSEOUT, 	function(event){button.hide()}, true )
		parentNode.addEventListener(ORYX.CONFIG.EVENT_MOUSEOUT, function (event) { hideButtons() }, true);
		//svgRootNode.addEventListener(	ORYX.CONFIG.EVENT_MOUSEOUT, 	function(event){ inCanvas = false } , true );

		// Hide the button initialy
		hideButtons();

		// Add the callbacks
		buttonGrow.addEventListener('click', function () { callback(position); showButtons(); }, true);
		buttonShrink.addEventListener('click', function () { callback(position, true); showButtons(); }, true);

	}


});

/**
 * Copyright (c) 2008
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.RenameShapes = Clazz.extend({

	facade: undefined,

	construct: function (facade) {

		this.facade = facade;

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DBLCLICK, this.actOnDBLClick.bind(this));
		this.facade.offer({
			keyCodes: [{
				keyCode: 113, // F2-Key
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.renamePerF2.bind(this)
		});


		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEDOWN, this.hide.bind(this), true)
	},

	/**
	 * This method handles the "F2" key down event. The selected shape are looked
	 * up and the editing of title/name of it gets started.
	 */
	renamePerF2: function () {
		var selectedShapes = this.facade.getSelection();
		this.actOnDBLClick(undefined, selectedShapes.first());
	},

	actOnDBLClick: function (evt, shape) {
		if (!(shape instanceof ORYX.Core.Shape)) { return }

		// Destroys the old input, if there is one
		this.destroy();

		// Get all properties which where at least one ref to view is set
		var props = shape.getStencil().properties().findAll(function (item) {
			return (item.refToView()
				&& item.refToView().length > 0
				&& item.directlyEditable());
		});
		// from these, get all properties where write access are and the type is String
		props = props.findAll(function (item) { return !item.readonly() && item.type() == ORYX.CONFIG.TYPE_STRING });

		// Get all ref ids
		var allRefToViews = props.collect(function (prop) { return prop.refToView() }).flatten().compact();
		// Get all labels from the shape with the ref ids
		var labels = shape.getLabels().findAll(function (label) { return allRefToViews.any(function (toView) { return label.id.endsWith(toView) }); })

		// If there are no referenced labels --> return
		if (labels.length == 0) { return }

		// Define the nearest label
		var nearestLabel = labels.length <= 1 ? labels[0] : null;
		if (!nearestLabel) {

			nearestLabel = labels.find(function (label) { return label.node == evt.target || label.node == evt.target.parentNode })
			if (!nearestLabel) {

				var evtCoord = this.facade.eventCoordinates(evt);

				var trans = this.facade.getCanvas().rootNode.lastChild.getScreenCTM();
				evtCoord.x *= trans.a;
				evtCoord.y *= trans.d;

				var diff = labels.collect(function (label) {
					var center = this.getCenterPosition(label.node);
					var len = Math.sqrt(Math.pow(center.x - evtCoord.x, 2) + Math.pow(center.y - evtCoord.y, 2));
					return { diff: len, label: label }
				}.bind(this));

				diff.sort(function (a, b) { return a.diff > b.diff })

				nearestLabel = diff[0].label;

			}
		}
		// Get the particular property for the label
		var prop = props.find(function (item) { return item.refToView().any(function (toView) { return nearestLabel.id == shape.id + toView }) });

		// Set all particular config values
		var htmlCont = this.facade.getCanvas().getHTMLContainer().id;

		// Get the center position from the nearest label
		var width = Math.min(Math.max(100, shape.bounds.width()), 200);
		var center = this.getCenterPosition(nearestLabel.node);
		center.x -= (width / 2);
		var propId = prop.prefix() + "-" + prop.id();

		// Set the config values for the TextField/Area
		var config = {
			renderTo: htmlCont,
			value: shape.properties[propId],
			x: (center.x < 10) ? 10 : center.x,
			y: center.y,
			width: width,
			style: 'position:absolute',
			allowBlank: prop.optional(),
			maxLength: prop.length(),
			emptyText: prop.title(),
			cls: 'x_form_text_set_absolute'
		}

		// Depending on the property, generate 
		// ether an TextArea or TextField
		if (prop.wrapLines()) {

			config.y -= (60 / 2);
			config['grow'] = true;
			this.shownTextField = new Ext.form.TextArea(config);
		} else {

			config.y -= (20 / 2);

			this.shownTextField = new Ext.form.TextField(config);
		}

		//focus
		this.shownTextField.focus();

		// Define event handler
		//	Blur 	-> Destroy
		//	Change 	-> Set new values					
		this.shownTextField.on('blur', this.destroy.bind(this))
		this.shownTextField.on('change', function (node, value) {
			var currentEl = shape;
			var oldValue = currentEl.properties[propId];
			var newValue = value;
			var facade = this.facade;

			if (oldValue != newValue) {
				// Implement the specific command for property change
				var commandClass = ORYX.Core.Command.extend({
					construct: function () {
						this.el = currentEl;
						this.propId = propId;
						this.oldValue = oldValue;
						this.newValue = newValue;
						this.facade = facade;
					},
					execute: function () {
						this.el.setProperty(this.propId, this.newValue);
						//this.el.update();
						this.facade.setSelection([this.el]);
						this.facade.getCanvas().update();
						this.facade.updateSelection();
					},
					rollback: function () {
						this.el.setProperty(this.propId, this.oldValue);
						//this.el.update();
						this.facade.setSelection([this.el]);
						this.facade.getCanvas().update();
						this.facade.updateSelection();
					}
				})
				// Instanciated the class
				var command = new commandClass();

				// Execute the command
				this.facade.executeCommands([command]);
			}
		}.bind(this))

		// Diable the keydown in the editor (that when hitting the delete button, the shapes not get deleted)
		this.facade.disableEvent(ORYX.CONFIG.EVENT_KEYDOWN);

	},

	getCenterPosition: function (svgNode) {

		if (!svgNode) { return { x: 0, y: 0 } }

		var center = { x: 0, y: 0 };
		var trans, scale, transLocal, bounds;
		var useParent = false;
		try {

			if (('hidden' === svgNode.getAttributeNS(null, 'visibility') && svgNode.childNodes.length > 0)
				|| svgNode.childNodes.length === 0) {
				useParent = true;
			}

			var el = useParent ? svgNode.parentNode : svgNode;

			trans = el.getTransformToElement(this.facade.getCanvas().rootNode.lastChild);
			scale = this.facade.getCanvas().rootNode.lastChild.getScreenCTM();
			transLocal = el.getTransformToElement(el.parentNode);
		} catch (e) {
			return { x: 0, y: 0 }
		}

		center.x = trans.e - transLocal.e;
		center.y = trans.f - transLocal.f;


		try {
			bounds = svgNode.getBBox();

			if (!useParent && !(bounds.x < -1000)) {
				bounds.y -= 1;
			} else {
				bounds = { x: Number(svgNode.getAttribute('x')), y: Number(svgNode.getAttribute('y')), width: 0, height: 0 };
			}
		} catch (e) {
			bounds = { x: Number(svgNode.getAttribute('x')), y: Number(svgNode.getAttribute('y')), width: 0, height: 0 };
		}

		center.x += bounds.x;
		center.y += bounds.y;

		center.x += bounds.width / 2;
		center.y += bounds.height / 2;

		center.x *= scale.a;
		center.y *= scale.d;

		return center;
	},

	hide: function (e) {
		if (this.shownTextField && (!e || !this.shownTextField.el || e.target !== this.shownTextField.el.dom)) {
			this.shownTextField.onBlur();
		}
	},

	destroy: function (e) {
		if (this.shownTextField) {
			this.shownTextField.destroy();
			delete this.shownTextField;

			this.facade.enableEvent(ORYX.CONFIG.EVENT_KEYDOWN);
		}
	}
});
/**
 * Copyright (c) 2008
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/


/**
 * This plugin offer the functionality of undo/redo
 * Therewith the command pattern is used.
 * 
 * A Plugin which want that the changes could get undo/redo has 
 * to implement a command-class (which implements the method .execute(), .rollback()).
 * Those instance of class must be execute thru the facade.executeCommands(). If so,
 * those command get stored here in the undo/redo stack and can get reset/restore.
 *
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.Undo = Clazz.extend({

	// Defines the facade
	facade: undefined,

	// Defines the undo/redo Stack
	undoStack: [],
	redoStack: [],

	// Constructor 
	construct: function (facade) {

		this.facade = facade;

		// Offers the functionality of undo                
		this.facade.offer({
			name: ORYX.I18N.Undo.undo,
			description: ORYX.I18N.Undo.undoDesc,
			icon: ORYX.PATH + "/images/arrow_undo.png",
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 90,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.doUndo.bind(this),
			group: ORYX.I18N.Undo.group,
			isEnabled: function () { return this.undoStack.length > 0 }.bind(this),
			index: 0
		});

		// Offers the functionality of redo
		this.facade.offer({
			name: ORYX.I18N.Undo.redo,
			description: ORYX.I18N.Undo.redoDesc,
			icon: ORYX.PATH + "/images/arrow_redo.png",
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 89,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.doRedo.bind(this),
			group: ORYX.I18N.Undo.group,
			isEnabled: function () { return this.redoStack.length > 0 }.bind(this),
			index: 1
		});

		// Register on event for executing commands --> store all commands in a stack		 
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_EXECUTE_COMMANDS, this.handleExecuteCommands.bind(this));

	},

	/**
	 * Stores all executed commands in a stack
	 * 
	 * @param {Object} evt
	 */
	handleExecuteCommands: function (evt) {

		// If the event has commands
		if (!evt.commands) { return }

		// Add the commands to a undo stack ...
		this.undoStack.push(evt.commands);
		// ...and delete the redo stack
		this.redoStack = [];

		// Update
		this.facade.getCanvas().update();
		this.facade.updateSelection();

	},

	/**
	 * Does the undo
	 * 
	 */
	doUndo: function () {

		// Get the last commands
		var lastCommands = this.undoStack.pop();

		if (lastCommands) {
			// Add the commands to the redo stack
			this.redoStack.push(lastCommands);

			// Rollback every command
			for (var i = lastCommands.length - 1; i >= 0; --i) {
				lastCommands[i].rollback();
			}

			// Update and refresh the canvas
			//this.facade.getCanvas().update();
			//this.facade.updateSelection();
			this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_UNDO_ROLLBACK,
				commands: lastCommands
			});

			// Update
			this.facade.getCanvas().update();
			this.facade.updateSelection();
		}
	},

	/**
	 * Does the redo
	 * 
	 */
	doRedo: function () {

		// Get the last commands from the redo stack
		var lastCommands = this.redoStack.pop();

		if (lastCommands) {
			// Add this commands to the undo stack
			this.undoStack.push(lastCommands);

			// Execute those commands
			lastCommands.each(function (command) {
				command.execute();
			});

			// Update and refresh the canvas		
			//this.facade.getCanvas().update();
			//this.facade.updateSelection();
			this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_UNDO_EXECUTE,
				commands: lastCommands
			});

			// Update
			this.facade.getCanvas().update();
			this.facade.updateSelection();
		}
	}

});
/**
 * Copyright (c) 2008
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

/**
 * Supports EPCs by offering a syntax check and export and import ability..
 * 
 * 
 */
ORYX.Plugins.ProcessLink = Clazz.extend({

	facade: undefined,

	/**
	 * Offers the plugin functionality:
	 * 
	 */
	construct: function (facade) {

		this.facade = facade;

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_PROPERTY_CHANGED, this.propertyChanged.bind(this));

	},


	/**
	 * 
	 * @param {Object} option
	 */
	propertyChanged: function (option, node) {

		if (option.name !== "oryx-refuri" || !node instanceof ORYX.Core.Node) { return }


		if (option.value && option.value.length > 0 && option.value != "undefined") {

			this.show(node, option.value);

		} else {

			this.hide(node);

		}

	},

	/**
	 * Shows the Link for a particular shape with a specific url
	 * 
	 * @param {Object} shape
	 * @param {Object} url
	 */
	show: function (shape, url) {


		// Generate the svg-representation of a link
		var link = ORYX.Editor.graft("http://www.w3.org/2000/svg", null,
			['a',
				{ 'target': '_blank' },
				['path',
					{ "stroke-width": 1.0, "stroke": "#00DD00", "fill": "#00AA00", "d": "M3,3 l0,-2.5 l7.5,0 l0,-2.5 l7.5,4.5 l-7.5,3.5 l0,-2.5 l-8,0", "line-captions": "round" }
				]
			]);

		var link = ORYX.Editor.graft("http://www.w3.org/2000/svg", null,
			['a',
				{ 'target': '_blank' },
				['path', { "style": "fill:#92BFFC;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72", "d": "M0 1.44 L0 15.05 L11.91 15.05 L11.91 5.98 L7.37 1.44 L0 1.44 Z" }],
				['path', { "style": "stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72;fill:none;", "transform": "translate(7.5, -8.5)", "d": "M0 10.51 L0 15.05 L4.54 15.05" }],
				['path', { "style": "fill:#f28226;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72", "transform": "translate(-3, -1)", "d": "M0 8.81 L0 13.06 L5.95 13.06 L5.95 15.05 A50.2313 50.2313 -175.57 0 0 10.77 11.08 A49.9128 49.9128 -1.28 0 0 5.95 6.54 L5.95 8.81 L0 8.81 Z" }],
			]);

		/*
		 * 
		 * 					[ 'a',
							{'target': '_blank'},
							['path', { "style": "fill:none;stroke-width:0.5px; stroke:#000000", "d": "M7,4 l0,2"}],
							['path', { "style": "fill:none;stroke-width:0.5px; stroke:#000000", "d": "M4,8 l-2,0 l0,6"}],
							['path', { "style": "fill:none;stroke-width:0.5px; stroke:#000000", "d": "M10,8 l2,0 l0,6"}],
							['rect', { "style": "fill:#96ff96;stroke:#000000;stroke-width:1", "width": 6, "height": 4, "x": 4, "y": 0}],
							['rect', { "style": "fill:#ffafff;stroke:#000000;stroke-width:1", "width": 6, "height": 4, "x": 4, "y": 6}],
							['rect', { "style": "fill:#96ff96;stroke:#000000;stroke-width:1", "width": 6, "height": 4, "x": 0, "y": 12}],
							['rect', { "style": "fill:#96ff96;stroke:#000000;stroke-width:1", "width": 6, "height": 4, "x": 8, "y": 12}],
							['rect', { "style": "fill:none;stroke:none;pointer-events:all", "width": 14, "height": 16, "x": 0, "y": 0}]
						]);
		 */

		// Set the link with the special namespace
		link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", url);


		// Shows the link in the overlay					
		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_OVERLAY_SHOW,
			id: "arissupport.urlref_" + shape.id,
			shapes: [shape],
			node: link,
			nodePosition: "SE"
		});

	},

	/**
	 * Hides the Link for a particular shape
	 * 
	 * @param {Object} shape
	 */
	hide: function (shape) {

		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_OVERLAY_HIDE,
			id: "arissupport.urlref_" + shape.id
		});

	}
});/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

Array.prototype.insertFrom = function (from, to) {
	to = Math.max(0, to);
	from = Math.min(Math.max(0, from), this.length - 1);

	var el = this[from];
	var old = this.without(el);
	var newA = old.slice(0, to);
	newA.push(el);
	if (old.length > to) {
		newA = newA.concat(old.slice(to))
	};
	return newA;
}

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.Arrangement = ORYX.Plugins.AbstractPlugin.extend({

	facade: undefined,

	construct: function (facade) {
		this.facade = facade;

		// Z-Ordering
		/** Hide for SIGNAVIO 
		
		this.facade.offer({
			'name':ORYX.I18N.Arrangement.btf,
			'functionality': this.setZLevel.bind(this, this.setToTop),
			'group': ORYX.I18N.Arrangement.groupZ,
			'icon': ORYX.PATH + "/images/shape_move_front.png",
			'description': ORYX.I18N.Arrangement.btfDesc,
			'index': 1,
			'minShape': 1});
			
		this.facade.offer({
			'name':ORYX.I18N.Arrangement.btb,
			'functionality': this.setZLevel.bind(this, this.setToBack),
			'group': ORYX.I18N.Arrangement.groupZ,
			'icon': ORYX.PATH + "/images/shape_move_back.png",
			'description': ORYX.I18N.Arrangement.btbDesc,
			'index': 2,
			'minShape': 1});

		this.facade.offer({
			'name':ORYX.I18N.Arrangement.bf,
			'functionality': this.setZLevel.bind(this, this.setForward),
			'group': ORYX.I18N.Arrangement.groupZ,
			'icon': ORYX.PATH + "/images/shape_move_forwards.png",
			'description': ORYX.I18N.Arrangement.bfDesc,
			'index': 3,
			'minShape': 1});

		this.facade.offer({
			'name':ORYX.I18N.Arrangement.bb,
			'functionality': this.setZLevel.bind(this, this.setBackward),
			'group': ORYX.I18N.Arrangement.groupZ,
			'icon': ORYX.PATH + "/images/shape_move_backwards.png",
			'description': ORYX.I18N.Arrangement.bbDesc,
			'index': 4,
			'minShape': 1});

		// Aligment
		this.facade.offer({
			'name':ORYX.I18N.Arrangement.ab,
			'functionality': this.alignShapes.bind(this, [ORYX.CONFIG.EDITOR_ALIGN_BOTTOM]),
			'group': ORYX.I18N.Arrangement.groupA,
			'icon': ORYX.PATH + "/images/shape_align_bottom.png",
			'description': ORYX.I18N.Arrangement.abDesc,
			'index': 1,
			'minShape': 2});



		this.facade.offer({
			'name':ORYX.I18N.Arrangement.at,
			'functionality': this.alignShapes.bind(this, [ORYX.CONFIG.EDITOR_ALIGN_TOP]),
			'group': ORYX.I18N.Arrangement.groupA,
			'icon': ORYX.PATH + "/images/shape_align_top.png",
			'description': ORYX.I18N.Arrangement.atDesc,
			'index': 3,
			'minShape': 2});

		this.facade.offer({
			'name':ORYX.I18N.Arrangement.al,
			'functionality': this.alignShapes.bind(this, [ORYX.CONFIG.EDITOR_ALIGN_LEFT]),
			'group': ORYX.I18N.Arrangement.groupA,
			'icon': ORYX.PATH + "/images/shape_align_left.png",
			'description': ORYX.I18N.Arrangement.alDesc,
			'index': 4,
			'minShape': 2});

		this.facade.offer({
			'name':ORYX.I18N.Arrangement.ar,
			'functionality': this.alignShapes.bind(this, [ORYX.CONFIG.EDITOR_ALIGN_RIGHT]),
			'group': ORYX.I18N.Arrangement.groupA,
			'icon': ORYX.PATH + "/images/shape_align_right.png",
			'description': ORYX.I18N.Arrangement.arDesc,
			'index': 6,
			'minShape': 2});

		**/

		this.facade.offer({
			'name': ORYX.I18N.Arrangement.am,
			'functionality': this.alignShapes.bind(this, [ORYX.CONFIG.EDITOR_ALIGN_MIDDLE]),
			'group': ORYX.I18N.Arrangement.groupA,
			'icon': ORYX.PATH + "/images/shape_align_middle.png",
			'description': ORYX.I18N.Arrangement.amDesc,
			'index': 1,
			'minShape': 2
		});

		this.facade.offer({
			'name': ORYX.I18N.Arrangement.ac,
			'functionality': this.alignShapes.bind(this, [ORYX.CONFIG.EDITOR_ALIGN_CENTER]),
			'group': ORYX.I18N.Arrangement.groupA,
			'icon': ORYX.PATH + "/images/shape_align_center.png",
			'description': ORYX.I18N.Arrangement.acDesc,
			'index': 2,
			'minShape': 2
		});


		this.facade.offer({
			'name': ORYX.I18N.Arrangement.as,
			'functionality': this.alignShapes.bind(this, [ORYX.CONFIG.EDITOR_ALIGN_SIZE]),
			'group': ORYX.I18N.Arrangement.groupA,
			'icon': ORYX.PATH + "/images/shape_align_size.png",
			'description': ORYX.I18N.Arrangement.asDesc,
			'index': 3,
			'minShape': 2
		});



		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_ARRANGEMENT_TOP, this.setZLevel.bind(this, this.setToTop));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_ARRANGEMENT_BACK, this.setZLevel.bind(this, this.setToBack));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_ARRANGEMENT_FORWARD, this.setZLevel.bind(this, this.setForward));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_ARRANGEMENT_BACKWARD, this.setZLevel.bind(this, this.setBackward));


	},

	onSelectionChanged: function (elemnt) {
		var selection = this.facade.getSelection();
		if (selection.length === 1 && selection[0] instanceof ORYX.Core.Edge) {
			this.setToTop(selection);
		}
	},

	setZLevel: function (callback, event) {

		//Command-Pattern for dragging one docker
		var zLevelCommand = ORYX.Core.Command.extend({
			construct: function (callback, elements, facade) {
				this.callback = callback;
				this.elements = elements;
				// For redo, the previous elements get stored
				this.elAndIndex = elements.map(function (el) { return { el: el, previous: el.parent.children[el.parent.children.indexOf(el) - 1] } })
				this.facade = facade;
			},
			execute: function () {

				// Call the defined z-order callback with the elements
				this.callback(this.elements)
				this.facade.setSelection(this.elements)
			},
			rollback: function () {

				// Sort all elements on the index of there containment
				var sortedEl = this.elAndIndex.sortBy(function (el) {
					var value = el.el;
					var t = $A(value.node.parentNode.childNodes);
					return t.indexOf(value.node);
				});

				// Every element get setted back bevor the old previous element
				for (var i = 0; i < sortedEl.length; i++) {
					var el = sortedEl[i].el;
					var p = el.parent;
					var oldIndex = p.children.indexOf(el);
					var newIndex = p.children.indexOf(sortedEl[i].previous);
					newIndex = newIndex || 0
					p.children = p.children.insertFrom(oldIndex, newIndex)
					el.node.parentNode.insertBefore(el.node, el.node.parentNode.childNodes[newIndex + 1]);
				}

				// Reset the selection
				this.facade.setSelection(this.elements)
			}
		});

		// Instanziate the dockCommand
		var command = new zLevelCommand(callback, this.facade.getSelection(), this.facade);
		if (event.excludeCommand) {
			command.execute();
		} else {
			this.facade.executeCommands([command]);
		}

	},

	setToTop: function (elements) {

		// Sortieren des Arrays nach dem Index des SVGKnotens im Bezug auf dem Elternknoten.
		var tmpElem = elements.sortBy(function (value, index) {
			var t = $A(value.node.parentNode.childNodes);
			return t.indexOf(value.node);
		});
		// Sortiertes Array wird nach oben verschoben.
		tmpElem.each(function (value) {
			var p = value.parent;
			if (p.children.last() === value) {
				return;
			}
			p.children = p.children.without(value)
			p.children.push(value);
			value.node.parentNode.appendChild(value.node);
		});
	},

	setToBack: function (elements) {
		// Sortieren des Arrays nach dem Index des SVGKnotens im Bezug auf dem Elternknoten.
		var tmpElem = elements.sortBy(function (value, index) {
			var t = $A(value.node.parentNode.childNodes);
			return t.indexOf(value.node);
		});

		tmpElem = tmpElem.reverse();

		// Sortiertes Array wird nach unten verschoben.
		tmpElem.each(function (value) {
			var p = value.parent
			p.children = p.children.without(value)
			p.children.unshift(value);
			value.node.parentNode.insertBefore(value.node, value.node.parentNode.firstChild);
		});


	},

	setBackward: function (elements) {
		// Sortieren des Arrays nach dem Index des SVGKnotens im Bezug auf dem Elternknoten.
		var tmpElem = elements.sortBy(function (value, index) {
			var t = $A(value.node.parentNode.childNodes);
			return t.indexOf(value.node);
		});

		// Reverse the elements
		tmpElem = tmpElem.reverse();

		// Delete all Nodes who are the next Node in the nodes-Array
		var compactElem = tmpElem.findAll(function (el) { return !tmpElem.some(function (checkedEl) { return checkedEl.node == el.node.previousSibling }) });

		// Sortiertes Array wird nach eine Ebene nach oben verschoben.
		compactElem.each(function (el) {
			if (el.node.previousSibling === null) { return; }
			var p = el.parent;
			var index = p.children.indexOf(el);
			p.children = p.children.insertFrom(index, index - 1)
			el.node.parentNode.insertBefore(el.node, el.node.previousSibling);
		});


	},

	setForward: function (elements) {
		// Sortieren des Arrays nach dem Index des SVGKnotens im Bezug auf dem Elternknoten.
		var tmpElem = elements.sortBy(function (value, index) {
			var t = $A(value.node.parentNode.childNodes);
			return t.indexOf(value.node);
		});


		// Delete all Nodes who are the next Node in the nodes-Array
		var compactElem = tmpElem.findAll(function (el) { return !tmpElem.some(function (checkedEl) { return checkedEl.node == el.node.nextSibling }) });


		// Sortiertes Array wird eine Ebene nach unten verschoben.
		compactElem.each(function (el) {
			var nextNode = el.node.nextSibling
			if (nextNode === null) { return; }
			var index = el.parent.children.indexOf(el);
			var p = el.parent;
			p.children = p.children.insertFrom(index, index + 1)
			el.node.parentNode.insertBefore(nextNode, el.node);
		});
	},


	alignShapes: function (way) {

		var elements = this.facade.getSelection();

		// Set the elements to all Top-Level elements
		elements = this.facade.getCanvas().getShapesWithSharedParent(elements);
		// Get only nodes
		elements = elements.findAll(function (value) {
			return (value instanceof ORYX.Core.Node)
		});
		// Delete all attached intermediate events from the array
		elements = elements.findAll(function (value) {
			var d = value.getIncomingShapes()
			return d.length == 0 || !elements.include(d[0])
		});
		if (elements.length < 2) { return; }

		// get bounds of all shapes.
		var bounds = elements[0].absoluteBounds().clone();
		elements.each(function (shape) {
			bounds.include(shape.absoluteBounds().clone());
		});

		// get biggest width and heigth
		var maxWidth = 0;
		var maxHeight = 0;
		elements.each(function (shape) {
			maxWidth = Math.max(shape.bounds.width(), maxWidth);
			maxHeight = Math.max(shape.bounds.height(), maxHeight);
		});

		var commandClass = ORYX.Core.Command.extend({
			construct: function (elements, bounds, maxHeight, maxWidth, way, plugin) {
				this.elements = elements;
				this.bounds = bounds;
				this.maxHeight = maxHeight;
				this.maxWidth = maxWidth;
				this.way = way;
				this.facade = plugin.facade;
				this.plugin = plugin;
				this.orgPos = [];
			},
			setBounds: function (shape, maxSize) {
				if (!maxSize)
					maxSize = { width: ORYX.CONFIG.MAXIMUM_SIZE, height: ORYX.CONFIG.MAXIMUM_SIZE };

				if (!shape.bounds) { throw "Bounds not definined." }

				var newBounds = {
					a: {
						x: shape.bounds.upperLeft().x - (this.maxWidth - shape.bounds.width()) / 2,
						y: shape.bounds.upperLeft().y - (this.maxHeight - shape.bounds.height()) / 2
					},
					b: {
						x: shape.bounds.lowerRight().x + (this.maxWidth - shape.bounds.width()) / 2,
						y: shape.bounds.lowerRight().y + (this.maxHeight - shape.bounds.height()) / 2
					}
				}

				/* If the new width of shape exceeds the maximum width, set width value to maximum. */
				if (this.maxWidth > maxSize.width) {
					newBounds.a.x = shape.bounds.upperLeft().x -
						(maxSize.width - shape.bounds.width()) / 2;

					newBounds.b.x = shape.bounds.lowerRight().x + (maxSize.width - shape.bounds.width()) / 2
				}

				/* If the new height of shape exceeds the maximum height, set height value to maximum. */
				if (this.maxHeight > maxSize.height) {
					newBounds.a.y = shape.bounds.upperLeft().y -
						(maxSize.height - shape.bounds.height()) / 2;

					newBounds.b.y = shape.bounds.lowerRight().y + (maxSize.height - shape.bounds.height()) / 2
				}

				/* set bounds of shape */
				shape.bounds.set(newBounds);

			},
			execute: function () {
				// align each shape according to the way that was specified.
				this.elements.each(function (shape, index) {
					this.orgPos[index] = shape.bounds.upperLeft();

					var relBounds = this.bounds.clone();
					var newCoordinates;
					if (shape.parent && !(shape.parent instanceof ORYX.Core.Canvas)) {
						var upL = shape.parent.absoluteBounds().upperLeft();
						relBounds.moveBy(-upL.x, -upL.y);
					}

					switch (this.way) {
						// align the shapes in the requested way.
						case ORYX.CONFIG.EDITOR_ALIGN_BOTTOM:
							newCoordinates = {
								x: shape.bounds.upperLeft().x,
								y: relBounds.b.y - shape.bounds.height()
							}; break;

						case ORYX.CONFIG.EDITOR_ALIGN_MIDDLE:
							newCoordinates = {
								x: shape.bounds.upperLeft().x,
								y: (relBounds.a.y + relBounds.b.y - shape.bounds.height()) / 2
							}; break;

						case ORYX.CONFIG.EDITOR_ALIGN_TOP:
							newCoordinates = {
								x: shape.bounds.upperLeft().x,
								y: relBounds.a.y
							}; break;

						case ORYX.CONFIG.EDITOR_ALIGN_LEFT:
							newCoordinates = {
								x: relBounds.a.x,
								y: shape.bounds.upperLeft().y
							}; break;

						case ORYX.CONFIG.EDITOR_ALIGN_CENTER:
							newCoordinates = {
								x: (relBounds.a.x + relBounds.b.x - shape.bounds.width()) / 2,
								y: shape.bounds.upperLeft().y
							}; break;

						case ORYX.CONFIG.EDITOR_ALIGN_RIGHT:
							newCoordinates = {
								x: relBounds.b.x - shape.bounds.width(),
								y: shape.bounds.upperLeft().y
							}; break;

						case ORYX.CONFIG.EDITOR_ALIGN_SIZE:
							if (shape.isResizable) {
								this.orgPos[index] = { a: shape.bounds.upperLeft(), b: shape.bounds.lowerRight() };
								this.setBounds(shape, shape.maximumSize);
							}
							break;
					}

					if (newCoordinates) {
						var offset = {
							x: shape.bounds.upperLeft().x - newCoordinates.x,
							y: shape.bounds.upperLeft().y - newCoordinates.y
						}
						// Set the new position
						shape.bounds.moveTo(newCoordinates);
						this.plugin.layoutEdges(shape, shape.getAllDockedShapes(), offset);
						//shape.update()
					}
				}.bind(this));

				//this.facade.getCanvas().update();
				//this.facade.updateSelection();
			},
			rollback: function () {
				this.elements.each(function (shape, index) {
					if (this.way == ORYX.CONFIG.EDITOR_ALIGN_SIZE) {
						if (shape.isResizable) { shape.bounds.set(this.orgPos[index]); }
					} else { shape.bounds.moveTo(this.orgPos[index]); }
				}.bind(this));

				//this.facade.getCanvas().update();
				//this.facade.updateSelection();
			}
		})

		var command = new commandClass(elements, bounds, maxHeight, maxWidth, parseInt(way), this);

		this.facade.executeCommands([command]);
	}
});




if (!ORYX.Plugins)
	ORYX.Plugins = new Object();
/**
 * 发布工具栏
 * 1.定义ORYX.Plugins.Deploy类名
 * 2.plugins.xml注册插件
 * 
 * */
ORYX.Plugins.Deploy = Clazz.extend({
	facade: undefined,
	construct: function (facade) {
		this.facade = facade;
		//发布
		this.facade.offer({
			'name': ORYX.I18N.Deploy.deploy,
			'functionality': this.deploy.bind(this),
			'group': ORYX.I18N.Deploy.group,
			'icon': ORYX.PATH + "/images/deploy.png",
			'description': ORYX.I18N.Deploy.deployDesc,
			'index': 1,
			'minShape': 0,
			'maxShape': 0,
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 68, // ctrl+D 快捷方式
				keyAction: ORYX.CONFIG.KEY_ACTION_UP
			}
			]
		});

		//导入
		this.facade.offer({
			'name': ORYX.I18N.Deploy.export,
			'functionality': this.export.bind(this),
			'group': ORYX.I18N.Deploy.group,
			'icon': ORYX.PATH + "/images/export.png",
			'description': ORYX.I18N.Deploy.exportDesc,
			'index': 1,
			'minShape': 0,
			'maxShape': 0,
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 69, // ctrl+e 快捷方式
				keyAction: ORYX.CONFIG.KEY_ACTION_UP
			}
			]
		});
		//导出
		this.facade.offer({
			'name': ORYX.I18N.Deploy.import,
			'functionality': this.import.bind(this),
			'group': ORYX.I18N.Deploy.group,
			'icon': ORYX.PATH + "/images/import.png",
			'description': ORYX.I18N.Deploy.importDesc,
			'index': 1,
			'minShape': 0,
			'maxShape': 0,
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 73, // ctrl+I 快捷方式
				keyAction: ORYX.CONFIG.KEY_ACTION_UP
			}
			]
		});
	},
	/**
	 * 发布
	 */
	deploy: function () {
		var params = {
			ids: []
		};
		params.ids.push(getCurrentModelId());
		var param = Ext.encode(params);
		var completeCallback = function (success) {
			//Ext.getBody().mask('正在发布流程，请稍后...', "x-waiting-box");
			Ext.Ajax.request({
				url: getContextPath() + '/core/workflow/model/deploy',
				headers: { 'Content-Type': 'application/json' },
				method: 'post',
				async: false,
				params: param,
				success: function (response, opts) {
					var jsondata = eval('(' + response.responseText + ')');
					if (jsondata.state == 'OK') {
						Ext.Msg.alert('提示', '流程发布成功');
					}
					else {
						Ext.Msg.alert('错误', '流程发布错误:' + jsondata.msg);
					}
				},
				failure: function (response) {
					Ext.Msg.alert('流程发布错误', response.msg);
				}
			});
		}

		getEditor().raiseToolButtonAction('Save', completeCallback);

	},
	/**
	 * 导出
	 */
	export: function () {
		var iframe = document.getElementById("ifexport");
		if (!iframe) {
			var iframe = document.createElement('iframe');
			iframe.setAttribute('id', 'ifexport');
			iframe.setAttribute('style', 'display:none');
			document.body.appendChild(iframe);
		}
		iframe.setAttribute("src", getContextPath() + '/core/workflow/model/export?modelId=' + getCurrentModelId());
		iframe.onload = iframe.onreadystatechange = function iframeload() {
			if (!iframe.readyState || iframe.readyState == "complete") {
			}
		};
	},
	/**
	 * 导入
	 */
	import: function () {
		var uploadpanel = new Ext.form.FormPanel({
			anchor: '100% 100%'
			, bodyStyle: 'padding:10px'
			, fileUpload: true
			, items: [{
				xtype: 'textfield'
				, inputType: 'file'
				, name: 'file'
				, fieldLabel: '流程XML文件'
				, msgTarget: 'side'
				, allowBlank: false
				, anchor: '100%'
				, buttonText: '选择工作流xml文件...'
			}]
			, buttons: [{
				text: '导入',
				handler: function () {
					var form = uploadpanel.getForm();
					if (form.isValid()) {
						form.submit({
							url: getContextPath() + '/core/workflow/model/import?modelId=' + getCurrentModelId(),
							waitMsg: '正在上传文件...',
							failure: function (form, action) {
								if (action.result.state == 'OK') {
									Ext.Msg.alert('信息', '导入成功');
									location.reload();
								}
								else {
									Ext.Msg.alert('错误', action.result.msg);
								}
								win.close();
							}
						});
					}
				}
			}]
		});
		var win = new Ext.Window({
			title: '',
			layout: 'fit',
			width: 400,
			height: 200,
			closeAction: 'close',
			modal: true,
			items: [uploadpanel]
		});
		win.show();
	}
});

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
if (!ORYX.Plugins)
	ORYX.Plugins = new Object();
/**
 * 工具栏保存按钮
 */
ORYX.Plugins.Save = Clazz.extend({

	facade: undefined,

	processURI: undefined,

	changeSymbol: "*",

	construct: function (facade) {
		this.facade = facade;

		this.facade.offer({
			'name': ORYX.I18N.Save.save,
			'functionality': this.save.bind(this, false),
			'group': ORYX.I18N.Save.group,
			'icon': ORYX.PATH + "/images/disk.png",
			'description': ORYX.I18N.Save.saveDesc,
			'index': 1,
			'minShape': 0,
			'maxShape': 0,
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 83, // s-Keycode
				keyAction: ORYX.CONFIG.KEY_ACTION_UP
			}
			]
		});

		document.addEventListener("keydown", function (e) {
			if (e.ctrlKey && e.keyCode === 83) {
				Event.stop(e);
			}
		}, false)

		window.onbeforeunload = this.onUnLoad.bind(this)

		this.changeDifference = 0;

		// Register on event for executing commands --> store all commands in a stack		 
		// --> Execute
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_UNDO_EXECUTE, function () { this.changeDifference++; this.updateTitle(); }.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_EXECUTE_COMMANDS, function () { this.changeDifference++; this.updateTitle(); }.bind(this));
		// --> Rollback
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_UNDO_ROLLBACK, function () { this.changeDifference--; this.updateTitle(); }.bind(this));

		//TODO very critical for load time performance!!!
		//this.serializedDOM = DataManager.__persistDOM(this.facade);
	},

	updateTitle: function () {

		var value = window.document.title || document.getElementsByTagName("title")[0].childNodes[0].nodeValue;

		if (this.changeDifference === 0 && value.startsWith(this.changeSymbol)) {
			window.document.title = value.slice(1);
		} else if (this.changeDifference !== 0 && !value.startsWith(this.changeSymbol)) {
			window.document.title = this.changeSymbol + "" + value;
		}
	},

	onUnLoad: function () {
		if (this.changeDifference !== 0 || (this.facade.getModelMetaData()['new'] && this.facade.getCanvas().getChildShapes().size() > 0)) {
			return ORYX.I18N.Save.unsavedData;
		}

	},


	saveSynchronously: function (forceNew, modelInfo, completeCallback) {

		if (!modelInfo) {
			return;
		}

		var modelMeta = this.facade.getModelMetaData();
		var reqURI = modelMeta.modelHandler;


		// Get the stencilset
		var ss = this.facade.getStencilSets().values()[0]

		var typeTitle = ss.title();

		// Define Default values
		var name = (modelMeta["new"] && modelMeta.name === "") ? ORYX.I18N.Save.newProcess : modelInfo.name;
		var defaultData = { title: Signavio.Utils.escapeHTML(name || ""), summary: Signavio.Utils.escapeHTML(modelInfo.description || ""), type: typeTitle, url: reqURI, namespace: modelInfo.model.stencilset.namespace, comment: '' }

		// Create a Template
		var dialog = new Ext.XTemplate(
			// TODO find some nice words here -- copy from above ;)
			'<form class="oryx_repository_edit_model" action="#" id="edit_model" onsubmit="return false;">',

			'<fieldset>',
			'<p class="description">' + ORYX.I18N.Save.dialogDesciption + '</p>',
			'<input type="hidden" name="namespace" value="{namespace}" />',
			'<p><label for="edit_model_title">' + ORYX.I18N.Save.dialogLabelTitle + '</label><input type="text" class="text" name="title" value="{title}" id="edit_model_title" onfocus="this.className = \'text activated\'" onblur="this.className = \'text\'"/></p>',
			'<p><label for="edit_model_summary">' + ORYX.I18N.Save.dialogLabelDesc + '</label><textarea rows="5" name="summary" id="edit_model_summary" onfocus="this.className = \'activated\'" onblur="this.className = \'\'">{summary}</textarea></p>',
			(modelMeta.versioning) ? '<p><label for="edit_model_comment">' + ORYX.I18N.Save.dialogLabelComment + '</label><textarea rows="5" name="comment" id="edit_model_comment" onfocus="this.className = \'activated\'" onblur="this.className = \'\'">{comment}</textarea></p>' : '',
			'<p><label for="edit_model_type">' + ORYX.I18N.Save.dialogLabelType + '</label><input type="text" name="type" class="text disabled" value="{type}" disabled="disabled" id="edit_model_type" /></p>',

			'</fieldset>',

			'</form>')

		// Create the callback for the template
		callback = function (form) {

			// raise loading enable event
			/*this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_LOADING_ENABLE,
				text: ORYX.I18N.Save.saving
			});*/

			var title = form.elements["title"].value.strip();
			title = title.length == 0 ? defaultData.title : title;

			var summary = form.elements["summary"].value.strip();
			summary = summary.length == 0 ? defaultData.summary : summary;

			var namespace = form.elements["namespace"].value.strip();
			namespace = namespace.length == 0 ? defaultData.namespace : namespace;

			modelMeta.name = title;
			modelMeta.description = summary;
			modelMeta.parent = modelInfo.parent;
			modelMeta.namespace = namespace;

			//added changing title of page after first save, but with the changed flag
			if (!forceNew) window.document.title = this.changeSymbol + title + " | " + ORYX.CONFIG.APPNAME;


			// Get json
			var json = this.facade.getJSON();

			var glossary = [];

			//Support for glossary
			if (this.facade.hasGlossaryExtension) {

				Ext.apply(json, ORYX.Core.AbstractShape.JSONHelper);
				var allNodes = json.getChildShapes(true);

				var orders = {};

				this.facade.getGlossary().each(function (entry) {
					if ("undefined" == typeof orders[entry.shape.resourceId + "-" + entry.property.prefix() + "-" + entry.property.id()]) {
						orders[entry.shape.resourceId + "-" + entry.property.prefix() + "-" + entry.property.id()] = 0;
					}
					// Add entry
					glossary.push({
						itemId: entry.glossary,
						elementId: entry.shape.resourceId,
						propertyId: entry.property.prefix() + "-" + entry.property.id(),
						order: orders[entry.shape.resourceId + "-" + entry.property.prefix() + "-" + entry.property.id()]++
					});

					// Replace the property with the generated glossary url
					/*var rId = entry.shape.resourceId;
					var pKe = entry.property.id();
					for (var i=0, size=allNodes.length; i<size; ++i) {
						var sh = allNodes[i];
						if (sh.resourceId == rId) {
							for (var prop in sh.properties) {
								if (prop === pKe) {
									sh.properties[prop] = this.facade.generateGlossaryURL(entry.glossary, sh.properties[prop]);
									break;
								}
							}
							break;
						}
					}*/


					// Replace SVG
					if (entry.property.refToView() && entry.property.refToView().length > 0) {
						entry.property.refToView().each(function (ref) {
							var node = $(entry.shape.id + "" + ref);
							if (node)
								node.setAttribute("oryx:glossaryIds", entry.glossary + ";")
						})
					}
				}.bind(this))


				// Set the json as string
				json = json.serialize();

			} else {
				json = Ext.encode(json);
			}

			// Set the glossaries as string
			glossary = Ext.encode(glossary);

			var selection = this.facade.getSelection();
			this.facade.setSelection([]);

			// Get the serialized svg image source
			var svgClone = this.facade.getCanvas().getSVGRepresentation(true);
			this.facade.setSelection(selection);
			if (this.facade.getCanvas().properties["oryx-showstripableelements"] === false) {
				var stripOutArray = svgClone.getElementsByClassName("stripable-element");
				for (var i = stripOutArray.length - 1; i >= 0; i--) {
					stripOutArray[i].parentNode.removeChild(stripOutArray[i]);
				}
			}

			// Remove all forced stripable elements 
			var stripOutArray = svgClone.getElementsByClassName("stripable-element-force");
			for (var i = stripOutArray.length - 1; i >= 0; i--) {
				stripOutArray[i].parentNode.removeChild(stripOutArray[i]);
			}

			// Parse dom to string
			var svgDOM = DataManager.serialize(svgClone);

			var params = {
				json_xml: json,
				svg_xml: svgDOM,
				name: title,
				type: defaultData.type,
				parent: modelMeta.parent,
				description: summary,
				glossary_xml: glossary,
				namespace: modelMeta.namespace,
				views: Ext.util.JSON.encode(modelMeta.views || [])
			};

			var success = false;

			var successFn = function (transport) {
				var loc = transport.getResponseHeader.location;
				if (!this.processURI && loc) {
					this.processURI = loc;
				}

				if (forceNew) {
					var resJSON = transport.responseText.evalJSON();

					var modelURL = location.href.substring(0, location.href.indexOf(location.search)) + '?id=' + resJSON.href.substring(7);
					var newURLWin = new Ext.Window({
						title: ORYX.I18N.Save.savedAs,
						bodyStyle: "background:white;padding:10px",
						width: 'auto',
						height: 'auto',
						html: "<div style='font-weight:bold;margin-bottom:10px'>" + ORYX.I18N.Save.savedDescription + ":</div><span><a href='" + modelURL + "' target='_blank'>" + modelURL + "</a></span>",
						buttons: [{ text: 'Ok', handler: function () { newURLWin.destroy() } }]
					});
					newURLWin.show();

					window.open(modelURL);
				}

				//show saved status
				/*this.facade.raiseEvent({
						type:ORYX.CONFIG.EVENT_LOADING_STATUS,
						text:ORYX.I18N.Save.saved
					});*/

				success = true;

				win.close();

				if (success) {
					// Reset changes
					this.changeDifference = 0;
					this.updateTitle();

					// var resJSON = transport.responseText.evalJSON();
					// if (resJSON.modelId) {
					// 	modelMeta.modelId = resJSON.modelId;
					// }

					if (modelMeta["new"]) {
						modelMeta["new"] = false;
					}
				}
				if (typeof completeCallback == 'function') {
					completeCallback(success)
				}

				delete this.saving;

			}.bind(this);

			var failure = function (transport) {
				// raise loading disable event.
				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_LOADING_DISABLE
				});

				win.close();

				if (transport.status && transport.status === 401) {
					Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.notAuthorized).setIcon(Ext.Msg.WARNING).getDialog().setWidth(260).center().syncSize();
				} else if (transport.status && transport.status === 403) {
					Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.noRights).setIcon(Ext.Msg.WARNING).getDialog().setWidth(260).center().syncSize();
				} else if (transport.statusText === "transaction aborted") {
					Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.transAborted).setIcon(Ext.Msg.WARNING).getDialog().setWidth(260).center().syncSize();
				} else if (transport.statusText === "communication failure") {
					Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.comFailed).setIcon(Ext.Msg.WARNING).getDialog().setWidth(260).center().syncSize();
				} else {
					Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.failed).setIcon(Ext.Msg.WARNING).getDialog().setWidth(260).center().syncSize();
				}
				if (completeCallback) {
					completeCallback(false);
				}
				delete this.saving;

			}.bind(this);

			if (modelMeta["new"]) {
				// Send the request out
				params.id = modelMeta.modelId;
				this.sendSaveRequest('POST', reqURI, params, true, successFn, failure);
			} else if (forceNew) {
				this.sendSaveRequest('POST', reqURI, params, true, successFn, failure);
			} else {
				params.id = modelMeta.modelId;
				// Send the request out
				this.sendSaveRequest('PUT', reqURI, params, false, successFn, failure);
			}
		}.bind(this);
		win = new Ext.Window({
			id: 'Propertie_Window',
			width: 'auto',
			height: 'auto',
			title: forceNew ? ORYX.I18N.Save.saveAsTitle : ORYX.I18N.Save.save,
			modal: true,
			resize: false,
			bodyStyle: 'background:#FFFFFF',
			html: dialog.apply(defaultData),
			defaultButton: 0,
			buttons: [{
				text: ORYX.I18N.Save.saveBtn,
				handler: function () {
					win.body.mask(ORYX.I18N.Save.pleaseWait, "x-waiting-box");
					window.setTimeout(function () {
						callback($('edit_model'));
					}.bind(this), 10);
				},
				listeners: {
					render: function () {
						this.focus();
					}
				}
			}, {
				text: ORYX.I18N.Save.close,
				handler: function () {
					win.close();
				}.bind(this)
			}],
			listeners: {
				close: function () {
					win.destroy();
					delete this.saving;
				}.bind(this)
			}
		});
		win.show();
	},

	/**
	 * Get the model data and call the success callback
	 * 
	 * @param {Function} success Success callback
	 */
	retrieveModelData: function (success, completeCallback) {

		var onComplete = function () {
			Ext.getBody().unmask();
		}

		var modelMeta = this.facade.getModelMetaData();

		new Ajax.Request(ORYX.CONFIG.SERVLET_HANDLER_ROOT + "/model/" + modelMeta.modelId + "/json", {
			method: 'get',
			asynchronous: true,
			requestHeaders: {
				"Accept": "application/json"
			},
			encoding: 'UTF-8',
			onSuccess: (function (transport) {
				modelInfo = (transport.responseText || "{}").evalJSON();
				onComplete();
				success(modelInfo, completeCallback);
			}).bind(this),
			onException: function () {
				// raise loading disable event.
				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_LOADING_DISABLE
				});

				delete this.saving;
				onComplete();
				Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.exception).setIcon(Ext.Msg.ERROR).getDialog().setWidth(260).syncSize();
			}.bind(this),
			onFailure: (function (transport) {
				// raise loading disable event.
				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_LOADING_DISABLE
				});

				delete this.saving;
				onComplete();
				Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.failed).setIcon(Ext.Msg.ERROR).getDialog().setWidth(260).syncSize();
			}).bind(this),
			on401: (function (transport) {
				// raise loading disable event.
				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_LOADING_DISABLE
				});

				delete this.saving;
				onComplete();
				Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.notAuthorized).setIcon(Ext.Msg.WARNING).getDialog().setWidth(260).syncSize();
			}).bind(this),
			on403: (function (transport) {
				// raise loading disable event.
				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_LOADING_DISABLE
				});

				delete this.saving;
				onComplete();
				Ext.Msg.alert(ORYX.I18N.Oryx.title, ORYX.I18N.Save.noRights).setIcon(Ext.Msg.ERROR).getDialog().setWidth(260).syncSize();
			}).bind(this)
		});
	},

	sendSaveRequest: function (method, url, params, forceNew, success, failure) {

		var saveUri;
		if (forceNew == false) {
			saveUri = ORYX.CONFIG.SERVLET_HANDLER_ROOT + "/model/" + params.id + "/save";
		} else {
			saveUri = ORYX.CONFIG.SERVLET_HANDLER_ROOT + "/model/new";
		}

		// Send the request to the server.
		Ext.Ajax.request({
			url: saveUri,
			method: method,
			timeout: 1800000,
			disableCaching: true,
			headers: { 'Accept': "application/json", 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
			params: params,
			success: success,
			failure: failure
		});
	},

    /**
     * Saves the current process to the server.
     */
	save: function (forceNew, event, completeCallback) {

		// Check if currently is saving
		if (this.saving) {
			return;
		}

		this.saving = true;

		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_ABOUT_TO_SAVE
		});

		// ... save synchronously
		window.setTimeout((function () {
			var meta = this.facade.getModelMetaData();
			// Check if new...
			if (meta["new"]) {
				this.saveSynchronously(forceNew, meta, completeCallback);
			} else {
				Ext.getBody().mask(ORYX.I18N.Save.retrieveData, "x-waiting-box");
				// ...otherwise, get the current model data first.
				this.retrieveModelData(this.saveSynchronously.bind(this, forceNew), completeCallback)
			}
		}).bind(this), 10);


		return true;
	}
});
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * 获取ContextPath by gk
 */
function getContextPath() {
	var pathName = document.location.pathname;
	var index = pathName.substr(1).indexOf("/");
	var result = pathName.substr(0, index + 1);
	return result;
}
/**
 * 根据数据构建树 by gk
 * data 格式 必须包含 id, text , pid 三个属性
 */
function toTreeData(data) {
	var pos = {};
	var tree = [];
	var i = 0;
	while (data.length != 0) {
		if (data[i].pid == 0 || data[i].pid == '') {
			tree.push({
				id: data[i].id,
				text: data[i].text,
				leaf: true,
				children: [],
				data: data[i]
			});
			pos[data[i].id] = [tree.length - 1];
			data.splice(i, 1);
			i--;
		} else {
			var posArr = pos[data[i].pid];
			if (posArr != undefined) {
				var obj = tree[posArr[0]];
				for (var j = 1; j < posArr.length; j++) {
					obj = obj.children[posArr[j]];
				}
				obj.children.push({
					id: data[i].id,
					text: data[i].text,
					leaf: true,
					children: [],
					data: data[i]
				});
				obj.leaf = false;
				pos[data[i].id] = posArr.concat([obj.children.length - 1]);
				data.splice(i, 1);
				i--;
			} else {
				tree.push({
					id: data[i].id,
					text: data[i].text,
					leaf: true,
					children: [],
					data: data[i]
				});
				pos[data[i].id] = [tree.length - 1];
				data.splice(i, 1);
				i--;
			}
		}
		i++;
		if (i > data.length - 1) {
			i = 0;
		}
	}
	return tree;
}
/**
 * 根据数据构建树 by gk
 * data 格式 必须包含 id, text , pid 三个属性
 */
function toTreeNodes(data) {
	var pos = {};
	var tree = [];
	var i = 0;
	while (data.length != 0) {
		if (data[i].pid == 0 || data[i].pid == '') {
			tree.push(new Ext.tree.TreeNode({
				id: data[i].id,
				text: data[i].text,
				//leaf: true,
				data: data[i]
			}));
			pos[data[i].id] = [tree.length - 1];
			data.splice(i, 1);
			i--;
		} else {
			var posArr = pos[data[i].pid];
			if (posArr != undefined) {
				var obj = tree[posArr[0]];
				for (var j = 1; j < posArr.length; j++) {
					obj = obj.children[posArr[j]];
				}
				obj.appendChild(new Ext.tree.TreeNode({
					id: data[i].id,
					text: data[i].text,
					//leaf: true,
					data: data[i]
				}));
				obj.leaf = false;
				pos[data[i].id] = posArr.concat([obj.children.length - 1]);
				data.splice(i, 1);
				i--;
			} else {
				tree.push(new Ext.tree.TreeNode({
					id: data[i].id,
					text: data[i].text,
					//leaf: true,
					data: data[i]
				}));
				pos[data[i].id] = [tree.length - 1];
				data.splice(i, 1);
				i--;
			}
		}
		i++;
		if (i > data.length - 1) {
			i = 0;
		}
	}
	return tree;
}
/**
 *  获取url的参数值
 * @param name 参数的名称
 */
function getQueryString(name) {
	var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
	var r = window.location.search.substr(1).match(reg);
	if (r != null) {
		return unescape(r[2]);
	}
	return null;
}

/**
 * 获取当前打开流程的机构ID
 * 如果没有id就返回空 by gk
 */
function getCurrentOrgId() {
	return getQueryString('orgid');
}
/**
 * 获取当前打开流程的模型ID
 * 如果没有id就返回空 by gk
 */
function getCurrentModelId() {
	return getQueryString('id');
}

/**
 * @namespace Oryx name space for plugins
 * @name ORYX.Plugins
*/
if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

/**
 * The view plugin offers all of zooming functionality accessible over the 
 * tool bar. This are zoom in, zoom out, zoom to standard, zoom fit to model.
 * 
 * @class ORYX.Plugins.View
 * @extends Clazz
 * @param {Object} facade The editor facade for plugins.
*/
ORYX.Plugins.View = {
	/** @lends ORYX.Plugins.View.prototype */
	facade: undefined,

	construct: function (facade, ownPluginData) {
		this.facade = facade;
		//Standard Values
		this.zoomLevel = 1.0;
		this.maxFitToScreenLevel = 1.5;
		this.minZoomLevel = 0.1;
		this.maxZoomLevel = 2.5;
		this.diff = 5; //difference between canvas and view port, s.th. like toolbar??

		//Read properties
		ownPluginData.properties.each(function (property) {
			if (property.zoomLevel) { this.zoomLevel = Number(1.0); }
			if (property.maxFitToScreenLevel) { this.maxFitToScreenLevel = Number(property.maxFitToScreenLevel); }
			if (property.minZoomLevel) { this.minZoomLevel = Number(property.minZoomLevel); }
			if (property.maxZoomLevel) { this.maxZoomLevel = Number(property.maxZoomLevel); }
		}.bind(this));


		/* 注册放大工具栏按钮 */
		this.facade.offer({
			'name': ORYX.I18N.View.zoomIn,
			'functionality': this.zoom.bind(this, [1.0 + ORYX.CONFIG.ZOOM_OFFSET]),
			'group': ORYX.I18N.View.group,
			'icon': ORYX.PATH + "/images/magnifier_zoom_in.png",
			'description': ORYX.I18N.View.zoomOutDesc,
			'index': 1,
			'minShape': 0,
			'maxShape': 0,
			'isEnabled': function () { return this.zoomLevel < this.maxZoomLevel }.bind(this),
		});

		/* 注册缩小工具栏按钮*/
		this.facade.offer({
			'name': ORYX.I18N.View.zoomOut,
			'functionality': this.zoom.bind(this, [1.0 - ORYX.CONFIG.ZOOM_OFFSET]),
			'group': ORYX.I18N.View.group,
			'icon': ORYX.PATH + "/images/magnifier_zoom_out.png",
			'description': ORYX.I18N.View.zoomInDesc,
			'index': 2,
			'minShape': 0,
			'maxShape': 0,
			'isEnabled': function () { return this._checkSize() }.bind(this),
		});

		/* 注册标准大小工具栏按钮*/
		this.facade.offer({
			'name': ORYX.I18N.View.zoomStandard,
			'functionality': this.setAFixZoomLevel.bind(this, 1),
			'group': ORYX.I18N.View.group,
			'icon': ORYX.PATH + "/images/zoom_standard.png",
			'cls': 'icon-large',
			'description': ORYX.I18N.View.zoomStandardDesc,
			'index': 3,
			'minShape': 0,
			'maxShape': 0,
			'isEnabled': function () { return this.zoomLevel != 1 }.bind(this)
		});

		/* 最适合大小*/
		this.facade.offer({
			'name': ORYX.I18N.View.zoomFitToModel,
			'functionality': this.zoomFitToModel.bind(this),
			'group': ORYX.I18N.View.group,
			'icon': ORYX.PATH + "/images/image.png",
			'description': ORYX.I18N.View.zoomFitToModelDesc,
			'index': 4,
			'minShape': 0,
			'maxShape': 0
		});
	},

	/**
	 * It sets the zoom level to a fix value and call the zooming function.
	 * 
	 * @param {Number} zoomLevel
	 * 			the zoom level
	 */
	setAFixZoomLevel: function (zoomLevel) {
		this.zoomLevel = zoomLevel;
		this._checkZoomLevelRange();
		this.zoom(1);
	},

	/**
	 * It does the actual zooming. It changes the viewable size of the canvas 
	 * and all to its child elements.
	 * 
	 * @param {Number} factor
	 * 		the factor to adjust the zoom level
	 */
	zoom: function (factor) {
		// TODO: Zoomen auf allen Objekten im SVG-DOM

		this.zoomLevel *= factor;
		var scrollNode = this.facade.getCanvas().getHTMLContainer().parentNode.parentNode;
		var canvas = this.facade.getCanvas();
		var newWidth = canvas.bounds.width() * this.zoomLevel;
		var newHeight = canvas.bounds.height() * this.zoomLevel;

		/* Set new top offset */
		var offsetTop = (canvas.node.parentNode.parentNode.parentNode.offsetHeight - newHeight) / 2.0;
		offsetTop = offsetTop > 20 ? offsetTop - 20 : 0;
		canvas.node.parentNode.parentNode.style.marginTop = offsetTop + "px";
		offsetTop += 5;
		canvas.getHTMLContainer().style.top = offsetTop + "px";

		/*readjust scrollbar*/
		var newScrollTop = scrollNode.scrollTop - Math.round((canvas.getHTMLContainer().parentNode.getHeight() - newHeight) / 2) + this.diff;
		var newScrollLeft = scrollNode.scrollLeft - Math.round((canvas.getHTMLContainer().parentNode.getWidth() - newWidth) / 2) + this.diff;

		/* Set new Zoom-Level */
		canvas.setSize({ width: newWidth, height: newHeight }, true);

		/* Set Scale-Factor */
		canvas.node.setAttributeNS(null, "transform", "scale(" + this.zoomLevel + ")");

		/* Refresh the Selection */
		this.facade.updateSelection();
		scrollNode.scrollTop = newScrollTop;
		scrollNode.scrollLeft = newScrollLeft;

		/* Update the zoom-level*/
		canvas.zoomLevel = this.zoomLevel;
	},


	/**
	 * It calculates the zoom level to fit whole model into the visible area
	 * of the canvas. Than the model gets zoomed and the position of the 
	 * scroll bars are adjusted.
	 * 
	 */
	zoomFitToModel: function () {

		/* Get the size of the visible area of the canvas */
		var scrollNode = this.facade.getCanvas().getHTMLContainer().parentNode.parentNode;
		var visibleHeight = scrollNode.getHeight() - 30;
		var visibleWidth = scrollNode.getWidth() - 30;

		var nodes = this.facade.getCanvas().getChildShapes();

		if (!nodes || nodes.length < 1) {
			return false;
		}

		/* Calculate size of canvas to fit the model */
		var bounds = nodes[0].absoluteBounds().clone();
		nodes.each(function (node) {
			bounds.include(node.absoluteBounds().clone());
		});


		/* Set new Zoom Level */
		var scaleFactorWidth = visibleWidth / bounds.width();
		var scaleFactorHeight = visibleHeight / bounds.height();

		/* Choose the smaller zoom level to fit the whole model */
		var zoomFactor = scaleFactorHeight < scaleFactorWidth ? scaleFactorHeight : scaleFactorWidth;

		/*Test if maximum zoom is reached*/
		if (zoomFactor > this.maxFitToScreenLevel) { zoomFactor = this.maxFitToScreenLevel }
		/* Do zooming */
		this.setAFixZoomLevel(zoomFactor);

		/* Set scroll bar position */
		scrollNode.scrollTop = Math.round(bounds.upperLeft().y * this.zoomLevel) - 5;
		scrollNode.scrollLeft = Math.round(bounds.upperLeft().x * this.zoomLevel) - 5;

	},

	/**
	 * It checks if the zoom level is less or equal to the level, which is required
	 * to schow the whole canvas.
	 * 
	 * @private
	 */
	_checkSize: function () {
		var canvasParent = this.facade.getCanvas().getHTMLContainer().parentNode;
		var minForCanvas = Math.min((canvasParent.parentNode.getWidth() / canvasParent.getWidth()), (canvasParent.parentNode.getHeight() / canvasParent.getHeight()));
		return 1.05 > minForCanvas;

	},
	/**
	 * It checks if the zoom level is included in the definined zoom
	 * level range.
	 * 
	 * @private
	 */
	_checkZoomLevelRange: function () {
		/*var canvasParent=this.facade.getCanvas().getHTMLContainer().parentNode;
		var maxForCanvas= Math.max((canvasParent.parentNode.getWidth()/canvasParent.getWidth()),(canvasParent.parentNode.getHeight()/canvasParent.getHeight()));
		if(this.zoomLevel > maxForCanvas) {
			this.zoomLevel = maxForCanvas;			
		}*/
		if (this.zoomLevel < this.minZoomLevel) {
			this.zoomLevel = this.minZoomLevel;
		}

		if (this.zoomLevel > this.maxZoomLevel) {
			this.zoomLevel = this.maxZoomLevel;
		}
	}
};

ORYX.Plugins.View = Clazz.extend(ORYX.Plugins.View);
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.DragDropResize = ORYX.Plugins.AbstractPlugin.extend({

	/**
	 *	Constructor
	 *	@param {Object} Facade: The Facade of the Editor
	 */
	construct: function (facade) {
		this.facade = facade;

		// Initialize variables
		this.currentShapes = [];			// Current selected Shapes
		//this.pluginsData 		= [];			// Available Plugins
		this.toMoveShapes = [];			// Shapes there will be moved
		this.distPoints = [];			// Distance Points for Snap on Grid
		this.isResizing = false;		// Flag: If there was currently resized
		this.dragEnable = false;		// Flag: If Dragging is enabled
		this.dragIntialized = false;		// Flag: If the Dragging is initialized
		this.edgesMovable = true;			// Flag: If an edge is docked it is not movable
		this.offSetPosition = { x: 0, y: 0 };	// Offset of the Dragging
		this.faktorXY = { x: 1, y: 1 };	// The Current Zoom-Faktor
		this.containmentParentNode;				// the current future parent node for the dragged shapes
		this.isAddingAllowed = false;		// flag, if adding current selected shapes to containmentParentNode is allowed
		this.isAttachingAllowed = false;		// flag, if attaching to the current shape is allowed

		this.callbackMouseMove = this.handleMouseMove.bind(this);
		this.callbackMouseUp = this.handleMouseUp.bind(this);

		// Get the SVG-Containernode 
		var containerNode = this.facade.getCanvas().getSvgContainer();

		// Create the Selected Rectangle in the SVG
		this.selectedRect = new ORYX.Plugins.SelectedRect(containerNode);

		// Show grid line if enabled
		if (ORYX.CONFIG.SHOW_GRIDLINE) {
			this.vLine = new ORYX.Plugins.GridLine(containerNode, ORYX.Plugins.GridLine.DIR_VERTICAL);
			this.hLine = new ORYX.Plugins.GridLine(containerNode, ORYX.Plugins.GridLine.DIR_HORIZONTAL);
		}

		// Get a HTML-ContainerNode
		containerNode = this.facade.getCanvas().getHTMLContainer();

		this.scrollNode = this.facade.getCanvas().rootNode.parentNode.parentNode;

		// Create the southeastern button for resizing
		this.resizerSE = new ORYX.Plugins.Resizer(containerNode, "southeast", this.facade);
		this.resizerSE.registerOnResize(this.onResize.bind(this)); // register the resize callback
		this.resizerSE.registerOnResizeEnd(this.onResizeEnd.bind(this)); // register the resize end callback
		this.resizerSE.registerOnResizeStart(this.onResizeStart.bind(this)); // register the resize start callback

		// Create the northwestern button for resizing
		this.resizerNW = new ORYX.Plugins.Resizer(containerNode, "northwest", this.facade);
		this.resizerNW.registerOnResize(this.onResize.bind(this)); // register the resize callback
		this.resizerNW.registerOnResizeEnd(this.onResizeEnd.bind(this)); // register the resize end callback
		this.resizerNW.registerOnResizeStart(this.onResizeStart.bind(this)); // register the resize start callback

		// For the Drag and Drop
		// Register on MouseDown-Event on a Shape
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEDOWN, this.handleMouseDown.bind(this));
	},

	/**
	 * On Mouse Down
	 *
	 */
	handleMouseDown: function (event, uiObj) {
		// If the selection Bounds not intialized and the uiObj is not member of current selectio
		// then return
		if (!this.dragBounds || !this.currentShapes.member(uiObj) || !this.toMoveShapes.length) { return };

		// Start Dragging
		this.dragEnable = true;
		this.dragIntialized = true;
		this.edgesMovable = true;

		// Calculate the current zoom factor
		var a = this.facade.getCanvas().node.getScreenCTM();
		this.faktorXY.x = a.a;
		this.faktorXY.y = a.d;

		// Set the offset position of dragging
		var upL = this.dragBounds.upperLeft();
		this.offSetPosition = {
			x: Event.pointerX(event) - (upL.x * this.faktorXY.x),
			y: Event.pointerY(event) - (upL.y * this.faktorXY.y)
		};

		this.offsetScroll = { x: this.scrollNode.scrollLeft, y: this.scrollNode.scrollTop };

		// Register on Global Mouse-MOVE Event
		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this.callbackMouseMove, false);
		// Register on Global Mouse-UP Event
		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this.callbackMouseUp, true);

		return;
	},

	/**
	 * On Key Mouse Up
	 *
	 */
	handleMouseUp: function (event) {

		//disable containment highlighting
		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
			highlightId: "dragdropresize.contain"
		});

		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
			highlightId: "dragdropresize.attached"
		});

		// If Dragging is finished
		if (this.dragEnable) {

			// and update the current selection
			if (!this.dragIntialized) {

				// Do Method after Dragging
				this.afterDrag();

				// Check if the Shape is allowed to dock to the other Shape						
				if (this.isAttachingAllowed &&
					this.toMoveShapes.length == 1 && this.toMoveShapes[0] instanceof ORYX.Core.Node &&
					this.toMoveShapes[0].dockers.length > 0) {

					// Get the position and the docker					
					var position = this.facade.eventCoordinates(event);
					var docker = this.toMoveShapes[0].dockers[0];



					//Command-Pattern for dragging several Shapes
					var dockCommand = ORYX.Core.Command.extend({
						construct: function (docker, position, newDockedShape, facade) {
							this.docker = docker;
							this.newPosition = position;
							this.newDockedShape = newDockedShape;
							this.newParent = newDockedShape.parent || facade.getCanvas();
							this.oldPosition = docker.parent.bounds.center();
							this.oldDockedShape = docker.getDockedShape();
							this.oldParent = docker.parent.parent || facade.getCanvas();
							this.facade = facade;

							if (this.oldDockedShape) {
								this.oldPosition = docker.parent.absoluteBounds().center();
							}

						},
						execute: function () {
							this.dock(this.newDockedShape, this.newParent, this.newPosition);

							// Raise Event for having the docked shape on top of the other shape
							this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_ARRANGEMENT_TOP, excludeCommand: true })
						},
						rollback: function () {
							this.dock(this.oldDockedShape, this.oldParent, this.oldPosition);
						},
						dock: function (toDockShape, parent, pos) {
							// Add to the same parent Shape
							parent.add(this.docker.parent)


							// Set the Docker to the new Shape
							this.docker.setDockedShape(undefined);
							this.docker.bounds.centerMoveTo(pos)
							this.docker.setDockedShape(toDockShape);
							//this.docker.update();

							this.facade.setSelection([this.docker.parent]);
							this.facade.getCanvas().update();
							this.facade.updateSelection();


						}
					});

					// Instanziate the dockCommand
					var commands = [new dockCommand(docker, position, this.containmentParentNode, this.facade)];
					this.facade.executeCommands(commands);


					// Check if adding is allowed to the other Shape	
				} else if (this.isAddingAllowed) {


					// Refresh all Shapes --> Set the new Bounds
					this.refreshSelectedShapes();

				}

				this.facade.updateSelection();

				//this.currentShapes.each(function(shape) {shape.update()})
				// Raise Event: Dragging is finished
				this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_DRAGDROP_END });
			}

			if (this.vLine)
				this.vLine.hide();
			if (this.hLine)
				this.hLine.hide();
		}

		// Disable 
		this.dragEnable = false;


		// UnRegister on Global Mouse-UP/-Move Event
		document.documentElement.removeEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this.callbackMouseUp, true);
		document.documentElement.removeEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this.callbackMouseMove, false);

		return;
	},

	/**
	* On Key Mouse Move
	*
	*/
	handleMouseMove: function (event) {
		// If dragging is not enabled, go return
		if (!this.dragEnable) { return };
		// If Dragging is initialized
		if (this.dragIntialized) {
			// Raise Event: Drag will be started
			this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_DRAGDROP_START });
			this.dragIntialized = false;

			// And hide the resizers and the highlighting
			this.resizerSE.hide();
			this.resizerNW.hide();

			// if only edges are selected, containmentParentNode must be the canvas
			this._onlyEdges = this.currentShapes.all(function (currentShape) {
				return (currentShape instanceof ORYX.Core.Edge);
			});



			// Do method before Drag
			this.beforeDrag();

			this._currentUnderlyingNodes = [];

		}


		// Calculate the new position
		var position = {
			x: Event.pointerX(event) - this.offSetPosition.x,
			y: Event.pointerY(event) - this.offSetPosition.y
		}

		position.x -= this.offsetScroll.x - this.scrollNode.scrollLeft;
		position.y -= this.offsetScroll.y - this.scrollNode.scrollTop;

		// If not the Control-Key are pressed
		var modifierKeyPressed = event.shiftKey || event.ctrlKey;
		if (ORYX.CONFIG.GRID_ENABLED && !modifierKeyPressed) {
			// Snap the current position to the nearest Snap-Point
			position = this.snapToGrid(position);
		} else {
			if (this.vLine)
				this.vLine.hide();
			if (this.hLine)
				this.hLine.hide();
		}

		// Adjust the point by the zoom faktor 
		position.x /= this.faktorXY.x;
		position.y /= this.faktorXY.y;

		// Set that the position is not lower than zero
		position.x = Math.max(0, position.x)
		position.y = Math.max(0, position.y)

		// Set that the position is not bigger than the canvas
		var c = this.facade.getCanvas();
		position.x = Math.min(c.bounds.width() - this.dragBounds.width(), position.x)
		position.y = Math.min(c.bounds.height() - this.dragBounds.height(), position.y)


		// Drag this bounds
		this.dragBounds.moveTo(position);

		// Update all selected shapes and the selection rectangle
		//this.refreshSelectedShapes();
		this.resizeRectangle(this.dragBounds);

		this.isAttachingAllowed = false;

		//check, if a node can be added to the underlying node
		var underlyingNodes = $A(this.facade.getCanvas().getAbstractShapesAtPosition(this.facade.eventCoordinates(event)));

		var checkIfAttachable = this.toMoveShapes.length == 1 && this.toMoveShapes[0] instanceof ORYX.Core.Node && this.toMoveShapes[0].dockers.length > 0
		checkIfAttachable = checkIfAttachable && underlyingNodes.length != 1


		if (!checkIfAttachable &&
			underlyingNodes.length === this._currentUnderlyingNodes.length &&
			underlyingNodes.all(function (node, index) { return this._currentUnderlyingNodes[index] === node }.bind(this))) {

			return

		} else if (this._onlyEdges) {

			this.isAddingAllowed = true;
			this.containmentParentNode = this.facade.getCanvas();

		} else {

			/* Check the containment and connection rules */
			var options = {
				event: event,
				underlyingNodes: underlyingNodes,
				checkIfAttachable: checkIfAttachable
			};
			this.checkRules(options);

		}

		this._currentUnderlyingNodes = underlyingNodes.reverse();

		//visualize the containment result
		if (this.isAttachingAllowed) {

			this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
				highlightId: "dragdropresize.attached",
				elements: [this.containmentParentNode],
				style: ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_RECTANGLE,
				color: ORYX.CONFIG.SELECTION_VALID_COLOR
			});

		} else {

			this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
				highlightId: "dragdropresize.attached"
			});
		}

		if (!this.isAttachingAllowed) {
			if (this.isAddingAllowed) {

				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
					highlightId: "dragdropresize.contain",
					elements: [this.containmentParentNode],
					color: ORYX.CONFIG.SELECTION_VALID_COLOR
				});

			} else {

				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
					highlightId: "dragdropresize.contain",
					elements: [this.containmentParentNode],
					color: ORYX.CONFIG.SELECTION_INVALID_COLOR
				});

			}
		} else {
			this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE,
				highlightId: "dragdropresize.contain"
			});
		}

		// Stop the Event
		//Event.stop(event);
		return;
	},


	/**
	 *  Checks the containment and connection rules for the selected shapes.
	 */
	checkRules: function (options) {
		var event = options.event;
		var underlyingNodes = options.underlyingNodes;
		var checkIfAttachable = options.checkIfAttachable;
		var noEdges = options.noEdges;

		//get underlying node that is not the same than one of the currently selected shapes or
		// a child of one of the selected shapes with the highest z Order.
		// The result is a shape or the canvas
		this.containmentParentNode = underlyingNodes.reverse().find((function (node) {
			return (node instanceof ORYX.Core.Canvas) ||
				(((node instanceof ORYX.Core.Node) || ((node instanceof ORYX.Core.Edge) && !noEdges))
					&& (!(this.currentShapes.member(node) ||
						this.currentShapes.any(function (shape) {
							return (shape.children.length > 0 && shape.getChildNodes(true).member(node));
						}))));
		}).bind(this));

		if (checkIfAttachable) {

			this.isAttachingAllowed = this.facade.getRules().canConnect({
				sourceShape: this.containmentParentNode,
				edgeShape: this.toMoveShapes[0],
				targetShape: this.toMoveShapes[0]
			});

			if (this.isAttachingAllowed) {
				var point = this.facade.eventCoordinates(event);
				this.isAttachingAllowed = this.containmentParentNode.isPointOverOffset(point.x, point.y);
			}
		}

		if (!this.isAttachingAllowed) {
			//check all selected shapes, if they can be added to containmentParentNode
			this.isAddingAllowed = this.toMoveShapes.all((function (currentShape) {
				if (currentShape instanceof ORYX.Core.Edge ||
					currentShape instanceof ORYX.Core.Controls.Docker ||
					this.containmentParentNode === currentShape.parent) {
					return true;
				} else if (this.containmentParentNode !== currentShape) {

					if (!(this.containmentParentNode instanceof ORYX.Core.Edge) || !noEdges) {

						if (this.facade.getRules().canContain({
							containingShape: this.containmentParentNode,
							containedShape: currentShape
						})) {
							return true;
						}
					}
				}
				return false;
			}).bind(this));
		}

		if (!this.isAttachingAllowed && !this.isAddingAllowed &&
			(this.containmentParentNode instanceof ORYX.Core.Edge)) {
			options.noEdges = true;
			options.underlyingNodes.reverse();
			this.checkRules(options);
		}
	},

	/**
	 * Redraw the selected Shapes.
	 *
	 */
	refreshSelectedShapes: function () {
		// If the selection bounds not initialized, return
		if (!this.dragBounds) { return }

		// Calculate the offset between the bounds and the old bounds
		var upL = this.dragBounds.upperLeft();
		var oldUpL = this.oldDragBounds.upperLeft();
		var offset = {
			x: upL.x - oldUpL.x,
			y: upL.y - oldUpL.y
		};

		// Instanciate the dragCommand
		var commands = [new ORYX.Core.Command.Move(this.toMoveShapes, offset, this.containmentParentNode, this.currentShapes, this)];
		// If the undocked edges command is setted, add this command
		if (this._undockedEdgesCommand instanceof ORYX.Core.Command) {
			commands.unshift(this._undockedEdgesCommand);
		}
		// Execute the commands			
		this.facade.executeCommands(commands);

		// copy the bounds to the old bounds
		if (this.dragBounds)
			this.oldDragBounds = this.dragBounds.clone();

	},

	/**
	 * Callback for Resize
	 *
	 */
	onResize: function (bounds) {
		// If the selection bounds not initialized, return
		if (!this.dragBounds) { return }

		this.dragBounds = bounds;
		this.isResizing = true;

		// Update the rectangle 
		this.resizeRectangle(this.dragBounds);
	},

	onResizeStart: function () {
		this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_RESIZE_START });
	},

	onResizeEnd: function () {

		if (!(this.currentShapes instanceof Array) || this.currentShapes.length <= 0) {
			return;
		}

		// If Resizing finished, the Shapes will be resize
		if (this.isResizing) {

			var commandClass = ORYX.Core.Command.extend({
				construct: function (shape, newBounds, plugin) {
					this.shape = shape;
					this.oldBounds = shape.bounds.clone();
					this.newBounds = newBounds;
					this.plugin = plugin;
				},
				execute: function () {
					this.shape.bounds.set(this.newBounds.a, this.newBounds.b);
					this.update(this.getOffset(this.oldBounds, this.newBounds));

				},
				rollback: function () {
					this.shape.bounds.set(this.oldBounds.a, this.oldBounds.b);
					this.update(this.getOffset(this.newBounds, this.oldBounds))
				},

				getOffset: function (b1, b2) {
					return {
						x: b2.a.x - b1.a.x,
						y: b2.a.y - b1.a.y,
						xs: b2.width() / b1.width(),
						ys: b2.height() / b1.height()
					}
				},
				update: function (offset) {
					this.shape.getLabels().each(function (label) {
						label.changed();
					});

					var allEdges = [].concat(this.shape.getIncomingShapes())
						.concat(this.shape.getOutgoingShapes())
						// Remove all edges which are included in the selection from the list
						.findAll(function (r) { return r instanceof ORYX.Core.Edge }.bind(this))

					this.plugin.layoutEdges(this.shape, allEdges, offset);

					this.plugin.facade.setSelection([this.shape]);
					this.plugin.facade.getCanvas().update();
					this.plugin.facade.updateSelection();
				}
			});

			var bounds = this.dragBounds.clone();
			var shape = this.currentShapes[0];

			if (shape.parent) {
				var parentPosition = shape.parent.absoluteXY();
				bounds.moveBy(-parentPosition.x, -parentPosition.y);
			}

			var command = new commandClass(shape, bounds, this);

			this.facade.executeCommands([command]);

			this.isResizing = false;

			this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_RESIZE_END });
		}
	},


	/**
	 * Prepare the Dragging
	 *
	 */
	beforeDrag: function () {

		var undockEdgeCommand = ORYX.Core.Command.extend({
			construct: function (moveShapes) {
				this.dockers = moveShapes.collect(function (shape) { return shape instanceof ORYX.Core.Controls.Docker ? { docker: shape, dockedShape: shape.getDockedShape(), refPoint: shape.referencePoint } : undefined }).compact();
			},
			execute: function () {
				this.dockers.each(function (el) {
					el.docker.setDockedShape(undefined);
				})
			},
			rollback: function () {
				this.dockers.each(function (el) {
					el.docker.setDockedShape(el.dockedShape);
					el.docker.setReferencePoint(el.refPoint);
					//el.docker.update();
				})
			}
		});

		this._undockedEdgesCommand = new undockEdgeCommand(this.toMoveShapes);
		this._undockedEdgesCommand.execute();

	},

	hideAllLabels: function (shape) {

		// Hide all labels from the shape
		shape.getLabels().each(function (label) {
			label.hide();
		});
		// Hide all labels from docked shapes
		shape.getAllDockedShapes().each(function (dockedShape) {
			var labels = dockedShape.getLabels();
			if (labels.length > 0) {
				labels.each(function (label) {
					label.hide();
				});
			}
		});

		// Do this recursive for all child shapes
		// EXP-NICO use getShapes
		shape.getChildren().each((function (value) {
			if (value instanceof ORYX.Core.Shape)
				this.hideAllLabels(value);
		}).bind(this));
	},

	/**
	 * Finished the Dragging
	 *
	 */
	afterDrag: function () {

	},

	/**
	 * Show all Labels at these shape
	 * 
	 */
	showAllLabels: function (shape) {

		// Show the label of these shape
		//shape.getLabels().each(function(label) {
		for (var i = 0; i < shape.length; i++) {
			var label = shape[i];
			label.show();
		}//);
		// Show all labels at docked shapes
		//shape.getAllDockedShapes().each(function(dockedShape) {
		var allDockedShapes = shape.getAllDockedShapes()
		for (var i = 0; i < allDockedShapes.length; i++) {
			var dockedShape = allDockedShapes[i];
			var labels = dockedShape.getLabels();
			if (labels.length > 0) {
				labels.each(function (label) {
					label.show();
				});
			}
		}//);

		// Do this recursive
		//shape.children.each((function(value) {
		for (var i = 0; i < shape.children.length; i++) {
			var value = shape.children[i];
			if (value instanceof ORYX.Core.Shape)
				this.showAllLabels(value);
		}//).bind(this));
	},



	/**
	 * On the Selection-Changed
	 *
	 */
	onSelectionChanged: function (event) {
		var elements = event.elements;

		// Reset the drag-variables
		this.dragEnable = false;
		this.dragIntialized = false;
		this.resizerSE.hide();
		this.resizerNW.hide();

		// If there is no elements
		if (!elements || elements.length == 0) {
			// Hide all things and reset all variables
			this.selectedRect.hide();
			this.currentShapes = [];
			this.toMoveShapes = [];
			this.dragBounds = undefined;
			this.oldDragBounds = undefined;
		} else {

			// Set the current Shapes
			this.currentShapes = elements;

			// Get all shapes with the highest parent in object hierarchy (canvas is the top most parent)
			var topLevelElements = this.facade.getCanvas().getShapesWithSharedParent(elements);
			this.toMoveShapes = topLevelElements;

			this.toMoveShapes = this.toMoveShapes.findAll(function (shape) {
				return shape instanceof ORYX.Core.Node &&
					(shape.dockers.length === 0 || !elements.member(shape.dockers.first().getDockedShape()))
			});

			elements.each((function (shape) {
				if (!(shape instanceof ORYX.Core.Edge)) { return }

				var dks = shape.getDockers()

				var hasF = elements.member(dks.first().getDockedShape());
				var hasL = elements.member(dks.last().getDockedShape());


				/* Enable movement of undocked edges */
				if (!hasF && !hasL) {
					var isUndocked = !dks.first().getDockedShape() && !dks.last().getDockedShape()
					if (isUndocked) {
						this.toMoveShapes = this.toMoveShapes.concat(dks);
					}
				}

				if (shape.dockers.length > 2 && hasF && hasL) {
					this.toMoveShapes = this.toMoveShapes.concat(dks.findAll(function (el, index) { return index > 0 && index < dks.length - 1 }))
				}

			}).bind(this));

			// Calculate the new area-bounds of the selection
			var newBounds = undefined;
			this.toMoveShapes.each(function (value) {
				var shape = value;
				if (value instanceof ORYX.Core.Controls.Docker) {
					/* Get the Shape */
					shape = value.parent;
				}

				if (!newBounds) {
					newBounds = shape.absoluteBounds();
				}
				else {
					newBounds.include(shape.absoluteBounds());
				}
			}.bind(this));

			if (!newBounds) {
				elements.each(function (value) {
					if (!newBounds) {
						newBounds = value.absoluteBounds();
					} else {
						newBounds.include(value.absoluteBounds());
					}
				});
			}

			// Set the new bounds
			this.dragBounds = newBounds;
			this.oldDragBounds = newBounds.clone();

			// Update and show the rectangle
			this.resizeRectangle(newBounds);
			this.selectedRect.show();

			// Show the resize button, if there is only one element and this is resizeable
			if (elements.length == 1 && elements[0].isResizable) {
				var aspectRatio = elements[0].getStencil().fixedAspectRatio() ? elements[0].bounds.width() / elements[0].bounds.height() : undefined;
				this.resizerSE.setBounds(this.dragBounds, elements[0].minimumSize, elements[0].maximumSize, aspectRatio);
				this.resizerSE.show();
				this.resizerNW.setBounds(this.dragBounds, elements[0].minimumSize, elements[0].maximumSize, aspectRatio);
				this.resizerNW.show();
			} else {
				this.resizerSE.setBounds(undefined);
				this.resizerNW.setBounds(undefined);
			}

			// If Snap-To-Grid is enabled, the Snap-Point will be calculate
			if (ORYX.CONFIG.GRID_ENABLED) {

				// Reset all points
				this.distPoints = [];

				if (this.distPointTimeout)
					window.clearTimeout(this.distPointTimeout)

				this.distPointTimeout = window.setTimeout(function () {
					// Get all the shapes, there will consider at snapping
					// Consider only those elements who shares the same parent element
					var distShapes = this.facade.getCanvas().getChildShapes(true).findAll(function (value) {
						var parentShape = value.parent;
						while (parentShape) {
							if (elements.member(parentShape)) return false;
							parentShape = parentShape.parent
						}
						return true;
					})

					// The current selection will delete from this array
					//elements.each(function(shape) {
					//	distShapes = distShapes.without(shape);
					//});

					// For all these shapes
					distShapes.each((function (value) {
						if (!(value instanceof ORYX.Core.Edge)) {
							var ul = value.absoluteXY();
							var width = value.bounds.width();
							var height = value.bounds.height();

							// Add the upperLeft, center and lowerRight - Point to the distancePoints
							this.distPoints.push({
								ul: {
									x: ul.x,
									y: ul.y
								},
								c: {
									x: ul.x + (width / 2),
									y: ul.y + (height / 2)
								},
								lr: {
									x: ul.x + width,
									y: ul.y + height
								}
							});
						}
					}).bind(this));

				}.bind(this), 10)


			}
		}
	},

	/**
	 * Adjust an Point to the Snap Points
	 *
	 */
	snapToGrid: function (position) {

		// Get the current Bounds
		var bounds = this.dragBounds;

		var point = {};

		var ulThres = 6;
		var cThres = 10;
		var lrThres = 6;

		var scale = this.vLine ? this.vLine.getScale() : 1;

		var ul = { x: (position.x / scale), y: (position.y / scale) };
		var c = { x: (position.x / scale) + (bounds.width() / 2), y: (position.y / scale) + (bounds.height() / 2) };
		var lr = { x: (position.x / scale) + (bounds.width()), y: (position.y / scale) + (bounds.height()) };

		var offsetX, offsetY;
		var gridX, gridY;

		// For each distant point
		this.distPoints.each(function (value) {

			var x, y, gx, gy;
			if (Math.abs(value.c.x - c.x) < cThres) {
				x = value.c.x - c.x;
				gx = value.c.x;
			}/* else if (Math.abs(value.ul.x-ul.x) < ulThres){
				x = value.ul.x-ul.x;
				gx = value.ul.x;
			} else if (Math.abs(value.lr.x-lr.x) < lrThres){
				x = value.lr.x-lr.x;
				gx = value.lr.x;
			} */


			if (Math.abs(value.c.y - c.y) < cThres) {
				y = value.c.y - c.y;
				gy = value.c.y;
			}/* else if (Math.abs(value.ul.y-ul.y) < ulThres){
				y = value.ul.y-ul.y;
				gy = value.ul.y;
			} else if (Math.abs(value.lr.y-lr.y) < lrThres){
				y = value.lr.y-lr.y;
				gy = value.lr.y;
			} */

			if (x !== undefined) {
				offsetX = offsetX === undefined ? x : (Math.abs(x) < Math.abs(offsetX) ? x : offsetX);
				if (offsetX === x)
					gridX = gx;
			}

			if (y !== undefined) {
				offsetY = offsetY === undefined ? y : (Math.abs(y) < Math.abs(offsetY) ? y : offsetY);
				if (offsetY === y)
					gridY = gy;
			}
		});


		if (offsetX !== undefined) {
			ul.x += offsetX;
			ul.x *= scale;
			if (this.vLine && gridX)
				this.vLine.update(gridX);
		} else {
			ul.x = (position.x - (position.x % (ORYX.CONFIG.GRID_DISTANCE / 2)));
			if (this.vLine)
				this.vLine.hide()
		}

		if (offsetY !== undefined) {
			ul.y += offsetY;
			ul.y *= scale;
			if (this.hLine && gridY)
				this.hLine.update(gridY);
		} else {
			ul.y = (position.y - (position.y % (ORYX.CONFIG.GRID_DISTANCE / 2)));
			if (this.hLine)
				this.hLine.hide();
		}

		return ul;
	},

	showGridLine: function () {

	},


	/**
	 * Redraw of the Rectangle of the SelectedArea
	 * @param {Object} bounds
	 */
	resizeRectangle: function (bounds) {
		// Resize the Rectangle
		this.selectedRect.resize(bounds);
	}

});


ORYX.Plugins.SelectedRect = Clazz.extend({

	construct: function (parentId) {

		this.parentId = parentId;

		this.node = ORYX.Editor.graft("http://www.w3.org/2000/svg", $(parentId),
			['g']);

		this.dashedArea = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.node,
			['rect', {
				x: 0, y: 0,
				'stroke-width': 1, stroke: '#777777', fill: 'none',
				'stroke-dasharray': '2,2',
				'pointer-events': 'none'
			}]);

		this.hide();

	},

	hide: function () {
		this.node.setAttributeNS(null, 'display', 'none');
	},

	show: function () {
		this.node.setAttributeNS(null, 'display', '');
	},

	resize: function (bounds) {
		var upL = bounds.upperLeft();

		var padding = ORYX.CONFIG.SELECTED_AREA_PADDING;

		this.dashedArea.setAttributeNS(null, 'width', bounds.width() + 2 * padding);
		this.dashedArea.setAttributeNS(null, 'height', bounds.height() + 2 * padding);
		this.node.setAttributeNS(null, 'transform', "translate(" + (upL.x - padding) + ", " + (upL.y - padding) + ")");
	}


});

ORYX.Plugins.GridLine = Clazz.extend({

	construct: function (parentId, direction) {

		if (ORYX.Plugins.GridLine.DIR_HORIZONTAL !== direction && ORYX.Plugins.GridLine.DIR_VERTICAL !== direction) {
			direction = ORYX.Plugins.GridLine.DIR_HORIZONTAL
		}


		this.parent = $(parentId);
		this.direction = direction;
		this.node = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.parent,
			['g']);

		this.line = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.node,
			['path', {
				'stroke-width': 1, stroke: 'silver', fill: 'none',
				'stroke-dasharray': '5,5',
				'pointer-events': 'none'
			}]);

		this.hide();

	},

	hide: function () {
		this.node.setAttributeNS(null, 'display', 'none');
	},

	show: function () {
		this.node.setAttributeNS(null, 'display', '');
	},

	getScale: function () {
		try {
			return this.parent.parentNode.transform.baseVal.getItem(0).matrix.a;
		} catch (e) {
			return 1;
		}
	},

	update: function (pos) {

		if (this.direction === ORYX.Plugins.GridLine.DIR_HORIZONTAL) {
			var y = pos instanceof Object ? pos.y : pos;
			var cWidth = this.parent.parentNode.parentNode.width.baseVal.value / this.getScale();
			this.line.setAttributeNS(null, 'd', 'M 0 ' + y + ' L ' + cWidth + ' ' + y);
		} else {
			var x = pos instanceof Object ? pos.x : pos;
			var cHeight = this.parent.parentNode.parentNode.height.baseVal.value / this.getScale();
			this.line.setAttributeNS(null, 'd', 'M' + x + ' 0 L ' + x + ' ' + cHeight);
		}

		this.show();
	}


});

ORYX.Plugins.GridLine.DIR_HORIZONTAL = "hor";
ORYX.Plugins.GridLine.DIR_VERTICAL = "ver";

ORYX.Plugins.Resizer = Clazz.extend({

	construct: function (parentId, orientation, facade) {

		this.parentId = parentId;
		this.orientation = orientation;
		this.facade = facade;
		this.node = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", $(this.parentId),
			['div', { 'class': 'resizer_' + this.orientation, style: 'left:0px; top:0px;' }]);

		this.node.addEventListener(ORYX.CONFIG.EVENT_MOUSEDOWN, this.handleMouseDown.bind(this), true);
		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this.handleMouseUp.bind(this), true);
		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this.handleMouseMove.bind(this), false);

		this.dragEnable = false;
		this.offSetPosition = { x: 0, y: 0 };
		this.bounds = undefined;

		this.canvasNode = this.facade.getCanvas().node;

		this.minSize = undefined;
		this.maxSize = undefined;

		this.aspectRatio = undefined;

		this.resizeCallbacks = [];
		this.resizeStartCallbacks = [];
		this.resizeEndCallbacks = [];
		this.hide();

		// Calculate the Offset
		this.scrollNode = this.node.parentNode.parentNode.parentNode;


	},

	handleMouseDown: function (event) {
		this.dragEnable = true;

		this.offsetScroll = { x: this.scrollNode.scrollLeft, y: this.scrollNode.scrollTop };

		this.offSetPosition = {
			x: Event.pointerX(event) - this.position.x,
			y: Event.pointerY(event) - this.position.y
		};

		this.resizeStartCallbacks.each((function (value) {
			value(this.bounds);
		}).bind(this));

	},

	handleMouseUp: function (event) {
		this.dragEnable = false;
		this.containmentParentNode = null;
		this.resizeEndCallbacks.each((function (value) {
			value(this.bounds);
		}).bind(this));

	},

	handleMouseMove: function (event) {
		if (!this.dragEnable) { return }

		if (event.shiftKey || event.ctrlKey) {
			this.aspectRatio = this.bounds.width() / this.bounds.height();
		} else {
			this.aspectRatio = undefined;
		}

		var position = {
			x: Event.pointerX(event) - this.offSetPosition.x,
			y: Event.pointerY(event) - this.offSetPosition.y
		}


		position.x -= this.offsetScroll.x - this.scrollNode.scrollLeft;
		position.y -= this.offsetScroll.y - this.scrollNode.scrollTop;

		position.x = Math.min(position.x, this.facade.getCanvas().bounds.width())
		position.y = Math.min(position.y, this.facade.getCanvas().bounds.height())

		var offset = {
			x: position.x - this.position.x,
			y: position.y - this.position.y
		}

		if (this.aspectRatio) {
			// fixed aspect ratio
			newAspectRatio = (this.bounds.width() + offset.x) / (this.bounds.height() + offset.y);
			if (newAspectRatio > this.aspectRatio) {
				offset.x = this.aspectRatio * (this.bounds.height() + offset.y) - this.bounds.width();
			} else if (newAspectRatio < this.aspectRatio) {
				offset.y = (this.bounds.width() + offset.x) / this.aspectRatio - this.bounds.height();
			}
		}

		// respect minimum and maximum sizes of stencil
		if (this.orientation === "northwest") {
			if (this.bounds.width() - offset.x > this.maxSize.width) {
				offset.x = -(this.maxSize.width - this.bounds.width());
				if (this.aspectRatio)
					offset.y = this.aspectRatio * offset.x;
			}
			if (this.bounds.width() - offset.x < this.minSize.width) {
				offset.x = -(this.minSize.width - this.bounds.width());
				if (this.aspectRatio)
					offset.y = this.aspectRatio * offset.x;
			}
			if (this.bounds.height() - offset.y > this.maxSize.height) {
				offset.y = -(this.maxSize.height - this.bounds.height());
				if (this.aspectRatio)
					offset.x = offset.y / this.aspectRatio;
			}
			if (this.bounds.height() - offset.y < this.minSize.height) {
				offset.y = -(this.minSize.height - this.bounds.height());
				if (this.aspectRatio)
					offset.x = offset.y / this.aspectRatio;
			}
		} else { // defaults to southeast
			if (this.bounds.width() + offset.x > this.maxSize.width) {
				offset.x = this.maxSize.width - this.bounds.width();
				if (this.aspectRatio)
					offset.y = this.aspectRatio * offset.x;
			}
			if (this.bounds.width() + offset.x < this.minSize.width) {
				offset.x = this.minSize.width - this.bounds.width();
				if (this.aspectRatio)
					offset.y = this.aspectRatio * offset.x;
			}
			if (this.bounds.height() + offset.y > this.maxSize.height) {
				offset.y = this.maxSize.height - this.bounds.height();
				if (this.aspectRatio)
					offset.x = offset.y / this.aspectRatio;
			}
			if (this.bounds.height() + offset.y < this.minSize.height) {
				offset.y = this.minSize.height - this.bounds.height();
				if (this.aspectRatio)
					offset.x = offset.y / this.aspectRatio;
			}
		}

		if (this.orientation === "northwest") {
			var oldLR = { x: this.bounds.lowerRight().x, y: this.bounds.lowerRight().y };
			this.bounds.extend({ x: -offset.x, y: -offset.y });
			this.bounds.moveBy(offset);
		} else { // defaults to southeast
			this.bounds.extend(offset);
		}

		this.update();

		this.resizeCallbacks.each((function (value) {
			value(this.bounds);
		}).bind(this));

		Event.stop(event);

	},

	registerOnResizeStart: function (callback) {
		if (!this.resizeStartCallbacks.member(callback)) {
			this.resizeStartCallbacks.push(callback);
		}
	},

	unregisterOnResizeStart: function (callback) {
		if (this.resizeStartCallbacks.member(callback)) {
			this.resizeStartCallbacks = this.resizeStartCallbacks.without(callback);
		}
	},

	registerOnResizeEnd: function (callback) {
		if (!this.resizeEndCallbacks.member(callback)) {
			this.resizeEndCallbacks.push(callback);
		}
	},

	unregisterOnResizeEnd: function (callback) {
		if (this.resizeEndCallbacks.member(callback)) {
			this.resizeEndCallbacks = this.resizeEndCallbacks.without(callback);
		}
	},

	registerOnResize: function (callback) {
		if (!this.resizeCallbacks.member(callback)) {
			this.resizeCallbacks.push(callback);
		}
	},

	unregisterOnResize: function (callback) {
		if (this.resizeCallbacks.member(callback)) {
			this.resizeCallbacks = this.resizeCallbacks.without(callback);
		}
	},

	hide: function () {
		this.node.style.display = "none";
	},

	show: function () {
		if (this.bounds)
			this.node.style.display = "";
	},

	setBounds: function (bounds, min, max, aspectRatio) {
		this.bounds = bounds;

		if (!min)
			min = { width: ORYX.CONFIG.MINIMUM_SIZE, height: ORYX.CONFIG.MINIMUM_SIZE };

		if (!max)
			max = { width: ORYX.CONFIG.MAXIMUM_SIZE, height: ORYX.CONFIG.MAXIMUM_SIZE };

		this.minSize = min;
		this.maxSize = max;

		this.aspectRatio = aspectRatio;

		this.update();
	},

	update: function () {
		if (!this.bounds) { return; }

		var upL = this.bounds.upperLeft();

		if (this.bounds.width() < this.minSize.width) { this.bounds.set(upL.x, upL.y, upL.x + this.minSize.width, upL.y + this.bounds.height()) };
		if (this.bounds.height() < this.minSize.height) { this.bounds.set(upL.x, upL.y, upL.x + this.bounds.width(), upL.y + this.minSize.height) };
		if (this.bounds.width() > this.maxSize.width) { this.bounds.set(upL.x, upL.y, upL.x + this.maxSize.width, upL.y + this.bounds.height()) };
		if (this.bounds.height() > this.maxSize.height) { this.bounds.set(upL.x, upL.y, upL.x + this.bounds.width(), upL.y + this.maxSize.height) };

		var a = this.canvasNode.getScreenCTM();

		upL.x *= a.a;
		upL.y *= a.d;

		if (this.orientation === "northwest") {
			upL.x -= 13;
			upL.y -= 26;
		} else { // defaults to southeast
			upL.x += (a.a * this.bounds.width()) + 3;
			upL.y += (a.d * this.bounds.height()) + 3;
		}

		this.position = upL;

		this.node.style.left = this.position.x + "px";
		this.node.style.top = this.position.y + "px";
	}
});



/**
 * Implements a Command to move shapes
 * 
 */
ORYX.Core.Command.Move = ORYX.Core.Command.extend({
	construct: function (moveShapes, offset, parent, selectedShapes, plugin) {
		this.moveShapes = moveShapes;
		this.selectedShapes = selectedShapes;
		this.offset = offset;
		this.plugin = plugin;
		// Defines the old/new parents for the particular shape
		this.newParents = moveShapes.collect(function (t) { return parent || t.parent });
		this.oldParents = moveShapes.collect(function (shape) { return shape.parent });
		this.dockedNodes = moveShapes.findAll(function (shape) { return shape instanceof ORYX.Core.Node && shape.dockers.length == 1 }).collect(function (shape) { return { docker: shape.dockers[0], dockedShape: shape.dockers[0].getDockedShape(), refPoint: shape.dockers[0].referencePoint } });
	},
	execute: function () {
		this.dockAllShapes()
		// Moves by the offset
		this.move(this.offset);
		// Addes to the new parents
		this.addShapeToParent(this.newParents);
		// Set the selection to the current selection
		this.selectCurrentShapes();
		this.plugin.facade.getCanvas().update();
		this.plugin.facade.updateSelection();
	},
	rollback: function () {
		// Moves by the inverted offset
		var offset = { x: -this.offset.x, y: -this.offset.y };
		this.move(offset);
		// Addes to the old parents
		this.addShapeToParent(this.oldParents);
		this.dockAllShapes(true)

		// Set the selection to the current selection
		this.selectCurrentShapes();
		this.plugin.facade.getCanvas().update();
		this.plugin.facade.updateSelection();

	},
	move: function (offset, doLayout) {

		// Move all Shapes by these offset
		for (var i = 0; i < this.moveShapes.length; i++) {
			var value = this.moveShapes[i];
			value.bounds.moveBy(offset);

			if (value instanceof ORYX.Core.Node) {

				(value.dockers || []).each(function (d) {
					d.bounds.moveBy(offset);
				})

				// Update all Dockers of Child shapes
				/*var childShapesNodes = value.getChildShapes(true).findAll(function(shape){ return shape instanceof ORYX.Core.Node });							
				var childDockedShapes = childShapesNodes.collect(function(shape){ return shape.getAllDockedShapes() }).flatten().uniq();							
				var childDockedEdge = childDockedShapes.findAll(function(shape){ return shape instanceof ORYX.Core.Edge });							
				childDockedEdge = childDockedEdge.findAll(function(shape){ return shape.getAllDockedShapes().all(function(dsh){ return childShapesNodes.include(dsh) }) });							
				var childDockedDockers = childDockedEdge.collect(function(shape){ return shape.dockers }).flatten();
				
				for (var j = 0; j < childDockedDockers.length; j++) {
					var docker = childDockedDockers[j];
					if (!docker.getDockedShape() && !this.moveShapes.include(docker)) {
						//docker.bounds.moveBy(offset);
						//docker.update();
					}
				}*/


				var allEdges = [].concat(value.getIncomingShapes())
					.concat(value.getOutgoingShapes())
					// Remove all edges which are included in the selection from the list
					.findAll(function (r) { return r instanceof ORYX.Core.Edge && !this.moveShapes.any(function (d) { return d == r || (d instanceof ORYX.Core.Controls.Docker && d.parent == r) }) }.bind(this))
					// Remove all edges which are between the node and a node contained in the selection from the list
					.findAll(function (r) {
						return (r.dockers.first().getDockedShape() == value || !this.moveShapes.include(r.dockers.first().getDockedShape())) &&
							(r.dockers.last().getDockedShape() == value || !this.moveShapes.include(r.dockers.last().getDockedShape()))
					}.bind(this))

				// Layout all outgoing/incoming edges
				this.plugin.layoutEdges(value, allEdges, offset);


				var allSameEdges = [].concat(value.getIncomingShapes())
					.concat(value.getOutgoingShapes())
					// Remove all edges which are included in the selection from the list
					.findAll(function (r) { return r instanceof ORYX.Core.Edge && r.dockers.first().isDocked() && r.dockers.last().isDocked() && !this.moveShapes.include(r) && !this.moveShapes.any(function (d) { return d == r || (d instanceof ORYX.Core.Controls.Docker && d.parent == r) }) }.bind(this))
					// Remove all edges which are included in the selection from the list
					.findAll(function (r) { return this.moveShapes.indexOf(r.dockers.first().getDockedShape()) > i || this.moveShapes.indexOf(r.dockers.last().getDockedShape()) > i }.bind(this))

				for (var j = 0; j < allSameEdges.length; j++) {
					for (var k = 1; k < allSameEdges[j].dockers.length - 1; k++) {
						var docker = allSameEdges[j].dockers[k];
						if (!docker.getDockedShape() && !this.moveShapes.include(docker)) {
							docker.bounds.moveBy(offset);
						}
					}
				}

				/*var i=-1;
				var nodes = value.getChildShapes(true);
				var allEdges = [];
				while(++i<nodes.length){
					var edges = [].concat(nodes[i].getIncomingShapes())
						.concat(nodes[i].getOutgoingShapes())
						// Remove all edges which are included in the selection from the list
						.findAll(function(r){ return r instanceof ORYX.Core.Edge && !allEdges.include(r) && r.dockers.any(function(d){ return !value.bounds.isIncluded(d.bounds.center)})})
					allEdges = allEdges.concat(edges);
					if (edges.length <= 0){ continue }
					//this.plugin.layoutEdges(nodes[i], edges, offset);
				}*/
			}
		}

	},
	dockAllShapes: function (shouldDocked) {
		// Undock all Nodes
		for (var i = 0; i < this.dockedNodes.length; i++) {
			var docker = this.dockedNodes[i].docker;

			docker.setDockedShape(shouldDocked ? this.dockedNodes[i].dockedShape : undefined)
			if (docker.getDockedShape()) {
				docker.setReferencePoint(this.dockedNodes[i].refPoint);
				//docker.update();
			}
		}
	},

	addShapeToParent: function (parents) {

		// For every Shape, add this and reset the position		
		for (var i = 0; i < this.moveShapes.length; i++) {
			var currentShape = this.moveShapes[i];
			if (currentShape instanceof ORYX.Core.Node &&
				currentShape.parent !== parents[i]) {

				// Calc the new position
				var unul = parents[i].absoluteXY();
				var csul = currentShape.absoluteXY();
				var x = csul.x - unul.x;
				var y = csul.y - unul.y;

				// Add the shape to the new contained shape
				parents[i].add(currentShape);
				// Add all attached shapes as well
				currentShape.getOutgoingShapes((function (shape) {
					if (shape instanceof ORYX.Core.Node && !this.moveShapes.member(shape)) {
						parents[i].add(shape);
					}
				}).bind(this));

				// Set the new position
				if (currentShape instanceof ORYX.Core.Node && currentShape.dockers.length == 1) {
					var b = currentShape.bounds;
					x += b.width() / 2; y += b.height() / 2
					currentShape.dockers.first().bounds.centerMoveTo(x, y);
				} else {
					currentShape.bounds.moveTo(x, y);
				}

			}

			// Update the shape
			//currentShape.update();

		}
	},
	selectCurrentShapes: function () {
		this.plugin.facade.setSelection(this.selectedShapes);
	}
});

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.ShapeHighlighting = Clazz.extend({

	construct: function (facade) {

		this.parentNode = facade.getCanvas().getSvgContainer();

		// The parent Node
		this.node = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.parentNode,
			['g']);

		this.highlightNodes = {};

		facade.registerOnEvent(ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW, this.setHighlight.bind(this));
		facade.registerOnEvent(ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, this.hideHighlight.bind(this));

	},

	setHighlight: function (options) {
		if (options && options.highlightId) {
			var node = this.highlightNodes[options.highlightId];

			if (!node) {
				node = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.node,
					['path', {
						"stroke-width": 2.0, "fill": "none"
					}]);

				this.highlightNodes[options.highlightId] = node;
			}

			if (options.elements && options.elements.length > 0) {

				this.setAttributesByStyle(node, options);
				this.show(node);

			} else {

				this.hide(node);

			}

		}
	},

	hideHighlight: function (options) {
		if (options && options.highlightId && this.highlightNodes[options.highlightId]) {
			this.hide(this.highlightNodes[options.highlightId]);
		}
	},

	hide: function (node) {
		node.setAttributeNS(null, 'display', 'none');
	},

	show: function (node) {
		node.setAttributeNS(null, 'display', '');
	},

	setAttributesByStyle: function (node, options) {

		// If the style say, that it should look like a rectangle
		if (options.style && options.style == ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_RECTANGLE) {

			// Set like this
			var bo = options.elements[0].absoluteBounds();

			var strWidth = options.strokewidth ? options.strokewidth : ORYX.CONFIG.BORDER_OFFSET

			node.setAttributeNS(null, "d", this.getPathRectangle(bo.a, bo.b, strWidth));
			node.setAttributeNS(null, "stroke", options.color ? options.color : ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR);
			node.setAttributeNS(null, "stroke-opacity", options.opacity ? options.opacity : 0.2);
			node.setAttributeNS(null, "stroke-width", strWidth);

		} else if (options.elements.length == 1
			&& options.elements[0] instanceof ORYX.Core.Edge &&
			options.highlightId != "selection") {

			/* Highlight containment of edge's childs */
			node.setAttributeNS(null, "d", this.getPathEdge(options.elements[0].dockers));
			node.setAttributeNS(null, "stroke", options.color ? options.color : ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR);
			node.setAttributeNS(null, "stroke-opacity", options.opacity ? options.opacity : 0.2);
			node.setAttributeNS(null, "stroke-width", ORYX.CONFIG.OFFSET_EDGE_BOUNDS);

		} else {
			// If not, set just the corners
			node.setAttributeNS(null, "d", this.getPathByElements(options.elements));
			node.setAttributeNS(null, "stroke", options.color ? options.color : ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR);
			node.setAttributeNS(null, "stroke-opacity", options.opacity ? options.opacity : 1.0);
			node.setAttributeNS(null, "stroke-width", options.strokewidth ? options.strokewidth : 2.0);

		}
	},

	getPathByElements: function (elements) {
		if (!elements || elements.length <= 0) { return undefined }

		// Get the padding and the size
		var padding = ORYX.CONFIG.SELECTED_AREA_PADDING;

		var path = ""

		// Get thru all Elements
		elements.each((function (element) {
			if (!element) { return }
			// Get the absolute Bounds and the two Points
			var bounds = element.absoluteBounds();
			bounds.widen(padding)
			var a = bounds.upperLeft();
			var b = bounds.lowerRight();

			path = path + this.getPath(a, b);

		}).bind(this));

		return path;

	},

	getPath: function (a, b) {

		return this.getPathCorners(a, b);

	},

	getPathCorners: function (a, b) {

		var size = ORYX.CONFIG.SELECTION_HIGHLIGHT_SIZE;

		var path = ""

		// Set: Upper left 
		path = path + "M" + a.x + " " + (a.y + size) + " l0 -" + size + " l" + size + " 0 ";
		// Set: Lower left
		path = path + "M" + a.x + " " + (b.y - size) + " l0 " + size + " l" + size + " 0 ";
		// Set: Lower right
		path = path + "M" + b.x + " " + (b.y - size) + " l0 " + size + " l-" + size + " 0 ";
		// Set: Upper right
		path = path + "M" + b.x + " " + (a.y + size) + " l0 -" + size + " l-" + size + " 0 ";

		return path;
	},

	getPathRectangle: function (a, b, strokeWidth) {

		var size = ORYX.CONFIG.SELECTION_HIGHLIGHT_SIZE;

		var path = ""
		var offset = strokeWidth / 2.0;

		// Set: Upper left 
		path = path + "M" + (a.x + offset) + " " + (a.y);
		path = path + " L" + (a.x + offset) + " " + (b.y - offset);
		path = path + " L" + (b.x - offset) + " " + (b.y - offset);
		path = path + " L" + (b.x - offset) + " " + (a.y + offset);
		path = path + " L" + (a.x + offset) + " " + (a.y + offset);

		return path;
	},

	getPathEdge: function (edgeDockers) {
		var length = edgeDockers.length;
		var path = "M" + edgeDockers[0].bounds.center().x + " "
			+ edgeDockers[0].bounds.center().y;

		for (i = 1; i < length; i++) {
			var dockerPoint = edgeDockers[i].bounds.center();
			path = path + " L" + dockerPoint.x + " " + dockerPoint.y;
		}

		return path;
	}

});


ORYX.Plugins.HighlightingSelectedShapes = Clazz.extend({

	construct: function (facade) {
		this.facade = facade;
		this.opacityFull = 0.9;
		this.opacityLow = 0.4;

		// Register on Dragging-Events for show/hide of ShapeMenu
		//this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDROP_START, this.hide.bind(this));
		//this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDROP_END,  this.show.bind(this));		
	},

	/**
	 * On the Selection-Changed
	 *
	 */
	onSelectionChanged: function (event) {
		if (event.elements && event.elements.length > 1) {
			this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
				highlightId: 'selection',
				elements: event.elements.without(event.subSelection),
				color: ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR,
				opacity: !event.subSelection ? this.opacityFull : this.opacityLow
			});

			if (event.subSelection) {
				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
					highlightId: 'subselection',
					elements: [event.subSelection],
					color: ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR,
					opacity: this.opacityFull
				});
			} else {
				this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'subselection' });
			}

		} else {
			this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'selection' });
			this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'subselection' });
		}
	}
});

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.DragDocker = Clazz.extend({

	/**
	 *	Constructor
	 *	@param {Object} Facade: The Facade of the Editor
	 */
	construct: function (facade) {
		this.facade = facade;

		// Set the valid and invalid color
		this.VALIDCOLOR = ORYX.CONFIG.SELECTION_VALID_COLOR;
		this.INVALIDCOLOR = ORYX.CONFIG.SELECTION_INVALID_COLOR;

		// Define Variables 
		this.shapeSelection = undefined;
		this.docker = undefined;
		this.dockerParent = undefined;
		this.dockerSource = undefined;
		this.dockerTarget = undefined;
		this.lastUIObj = undefined;
		this.isStartDocker = undefined;
		this.isEndDocker = undefined;
		this.undockTreshold = 10;
		this.initialDockerPosition = undefined;
		this.outerDockerNotMoved = undefined;
		this.isValid = false;

		// For the Drag and Drop
		// Register on MouseDown-Event on a Docker
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEDOWN, this.handleMouseDown.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DOCKERDRAG, this.handleDockerDrag.bind(this));


		// Register on over/out to show / hide a docker
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEOVER, this.handleMouseOver.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEOUT, this.handleMouseOut.bind(this));


	},

	/**
	 * MouseOut Handler
	 *
	 */
	handleMouseOut: function (event, uiObj) {
		// If there is a Docker, hide this
		if (!this.docker && uiObj instanceof ORYX.Core.Controls.Docker) {
			uiObj.hide()
		} else if (!this.docker && uiObj instanceof ORYX.Core.Edge) {
			uiObj.dockers.each(function (docker) {
				docker.hide();
			})
		}
	},

	/**
	 * MouseOver Handler
	 *
	 */
	handleMouseOver: function (event, uiObj) {
		// If there is a Docker, show this		
		if (!this.docker && uiObj instanceof ORYX.Core.Controls.Docker) {
			uiObj.show()
		} else if (!this.docker && uiObj instanceof ORYX.Core.Edge) {
			uiObj.dockers.each(function (docker) {
				docker.show();
			})
		}
	},
	/**
	 * DockerDrag Handler
	 * delegates the uiEvent of the drag event to the mouseDown function
	 */
	handleDockerDrag: function (event, uiObj) {
		this.handleMouseDown(event.uiEvent, uiObj);
	},

	/**
	 * MouseDown Handler
	 *
	 */
	handleMouseDown: function (event, uiObj) {
		// If there is a Docker
		if (uiObj instanceof ORYX.Core.Controls.Docker && uiObj.isMovable) {

			/* Buffering shape selection and clear selection*/
			this.shapeSelection = this.facade.getSelection();
			this.facade.setSelection();

			this.docker = uiObj;
			this.initialDockerPosition = this.docker.bounds.center();
			this.outerDockerNotMoved = false;
			this.dockerParent = uiObj.parent;

			// Define command arguments
			this._commandArg = { docker: uiObj, dockedShape: uiObj.getDockedShape(), refPoint: uiObj.referencePoint || uiObj.bounds.center() };

			// Show the Docker
			this.docker.show();

			// If the Dockers Parent is an Edge, 
			//  and the Docker is either the first or last Docker of the Edge
			if (uiObj.parent instanceof ORYX.Core.Edge &&
				(uiObj.parent.dockers.first() == uiObj || uiObj.parent.dockers.last() == uiObj)) {

				// Get the Edge Source or Target
				if (uiObj.parent.dockers.first() == uiObj && uiObj.parent.dockers.last().getDockedShape()) {
					this.dockerTarget = uiObj.parent.dockers.last().getDockedShape()
				} else if (uiObj.parent.dockers.last() == uiObj && uiObj.parent.dockers.first().getDockedShape()) {
					this.dockerSource = uiObj.parent.dockers.first().getDockedShape()
				}

			} else {
				// If there parent is not an Edge, undefined the Source and Target
				this.dockerSource = undefined;
				this.dockerTarget = undefined;
			}

			this.isStartDocker = this.docker.parent.dockers.first() === this.docker
			this.isEndDocker = this.docker.parent.dockers.last() === this.docker

			// add to canvas while dragging
			this.facade.getCanvas().add(this.docker.parent);

			// Hide all Labels from Docker
			this.docker.parent.getLabels().each(function (label) {
				label.hide();
			});

			// Undocked the Docker from current Shape
			if ((!this.isStartDocker && !this.isEndDocker) || !this.docker.isDocked()) {

				this.docker.setDockedShape(undefined)
				// Set the Docker to the center of the mouse pointer
				var evPos = this.facade.eventCoordinates(event);
				this.docker.bounds.centerMoveTo(evPos);
				//this.docker.update()
				//this.facade.getCanvas().update();
				this.dockerParent._update();
			} else {
				this.outerDockerNotMoved = true;
			}

			var option = { movedCallback: this.dockerMoved.bind(this), upCallback: this.dockerMovedFinished.bind(this) }

			// Enable the Docker for Drag'n'Drop, give the mouseMove and mouseUp-Callback with
			ORYX.Core.UIEnableDrag(event, uiObj, option);
		}
	},

	/**
	 * Docker MouseMove Handler
	 *
	 */
	dockerMoved: function (event) {
		this.outerDockerNotMoved = false;
		var snapToMagnet = undefined;

		if (this.docker.parent) {
			if (this.isStartDocker || this.isEndDocker) {

				// Get the EventPosition and all Shapes on these point
				var evPos = this.facade.eventCoordinates(event);

				if (this.docker.isDocked()) {
					/* Only consider start/end dockers if they are moved over a treshold */
					var distanceDockerPointer =
						ORYX.Core.Math.getDistancePointToPoint(evPos, this.initialDockerPosition);
					if (distanceDockerPointer < this.undockTreshold) {
						this.outerDockerNotMoved = true;
						return;
					}

					/* Undock the docker */
					this.docker.setDockedShape(undefined)
					// Set the Docker to the center of the mouse pointer
					//this.docker.bounds.centerMoveTo(evPos);
					this.dockerParent._update();
				}

				var shapes = this.facade.getCanvas().getAbstractShapesAtPosition(evPos);

				// Get the top level Shape on these, but not the same as Dockers parent
				var uiObj = shapes.pop();
				if (this.docker.parent === uiObj) {
					uiObj = shapes.pop();
				}



				// If the top level Shape the same as the last Shape, then return
				if (this.lastUIObj == uiObj) {
					//return;

					// If the top level uiObj instance of Shape and this isn't the parent of the docker 
				}
				else
					if (uiObj instanceof ORYX.Core.Shape) {

						// Get the StencilSet of the Edge
						var sset = this.docker.parent.getStencil().stencilSet();

						// Ask by the StencilSet if the source, the edge and the target valid connections.
						if (this.docker.parent instanceof ORYX.Core.Edge) {

							var highestParent = this.getHighestParentBeforeCanvas(uiObj);
							/* Ensure that the shape to dock is not a child shape 
							 * of the same edge.
							 */
							if (highestParent instanceof ORYX.Core.Edge
								&& this.docker.parent === highestParent) {
								this.isValid = false;
								this.dockerParent._update();
								return;
							}
							this.isValid = false;
							var curObj = uiObj, orgObj = uiObj;
							while (!this.isValid && curObj && !(curObj instanceof ORYX.Core.Canvas)) {
								uiObj = curObj;
								this.isValid = this.facade.getRules().canConnect({
									sourceShape: this.dockerSource ? // Is there a docked source 
										this.dockerSource : // than set this
										(this.isStartDocker ? // if not and if the Docker is the start docker
											uiObj : // take the last uiObj
											undefined), // if not set it to undefined;
									edgeShape: this.docker.parent,
									targetShape: this.dockerTarget ? // Is there a docked target 
										this.dockerTarget : // than set this
										(this.isEndDocker ? // if not and if the Docker is not the start docker
											uiObj : // take the last uiObj
											undefined) // if not set it to undefined;
								});
								curObj = curObj.parent;
							}

							// Reset uiObj if no 
							// valid parent is found
							if (!this.isValid) {
								uiObj = orgObj;
							}

						}
						else {
							this.isValid = this.facade.getRules().canConnect({
								sourceShape: uiObj,
								edgeShape: this.docker.parent,
								targetShape: this.docker.parent
							});
						}

						// If there is a lastUIObj, hide the magnets
						if (this.lastUIObj) {
							this.hideMagnets(this.lastUIObj)
						}

						// If there is a valid connection, show the magnets
						if (this.isValid) {
							this.showMagnets(uiObj)
						}

						// Set the Highlight Rectangle by these value
						this.showHighlight(uiObj, this.isValid ? this.VALIDCOLOR : this.INVALIDCOLOR);

						// Buffer the current Shape
						this.lastUIObj = uiObj;
					}
					else {
						// If there is no top level Shape, then hide the highligting of the last Shape
						this.hideHighlight();
						this.lastUIObj ? this.hideMagnets(this.lastUIObj) : null;
						this.lastUIObj = undefined;
						this.isValid = false;
					}

				// Snap to the nearest Magnet
				if (this.lastUIObj && this.isValid && !(event.shiftKey || event.ctrlKey)) {
					snapToMagnet = this.lastUIObj.magnets.find(function (magnet) {
						return magnet.absoluteBounds().isIncluded(evPos)
					});

					if (snapToMagnet) {
						this.docker.bounds.centerMoveTo(snapToMagnet.absoluteCenterXY());
						//this.docker.update()
					}
				}
			}
		}
		// Snap to on the nearest Docker of the same parent
		if (!(event.shiftKey || event.ctrlKey) && !snapToMagnet) {
			var minOffset = ORYX.CONFIG.DOCKER_SNAP_OFFSET;
			var nearestX = minOffset + 1
			var nearestY = minOffset + 1

			var dockerCenter = this.docker.bounds.center();

			if (this.docker.parent) {

				this.docker.parent.dockers.each((function (docker) {
					if (this.docker == docker) {
						return
					};

					var center = docker.referencePoint ? docker.getAbsoluteReferencePoint() : docker.bounds.center();

					nearestX = Math.abs(nearestX) > Math.abs(center.x - dockerCenter.x) ? center.x - dockerCenter.x : nearestX;
					nearestY = Math.abs(nearestY) > Math.abs(center.y - dockerCenter.y) ? center.y - dockerCenter.y : nearestY;


				}).bind(this));

				if (Math.abs(nearestX) < minOffset || Math.abs(nearestY) < minOffset) {
					nearestX = Math.abs(nearestX) < minOffset ? nearestX : 0;
					nearestY = Math.abs(nearestY) < minOffset ? nearestY : 0;

					this.docker.bounds.centerMoveTo(dockerCenter.x + nearestX, dockerCenter.y + nearestY);
					//this.docker.update()
				} else {
					var previous = this.docker.parent.dockers[Math.max(this.docker.parent.dockers.indexOf(this.docker) - 1, 0)]
					var next = this.docker.parent.dockers[Math.min(this.docker.parent.dockers.indexOf(this.docker) + 1, this.docker.parent.dockers.length - 1)]

					if (previous && next && previous !== this.docker && next !== this.docker) {
						var cp = previous.bounds.center();
						var cn = next.bounds.center();
						var cd = this.docker.bounds.center();

						// Checks if the point is on the line between previous and next
						if (ORYX.Core.Math.isPointInLine(cd.x, cd.y, cp.x, cp.y, cn.x, cn.y, 10)) {
							// Get the rise
							var raise = (Number(cn.y) - Number(cp.y)) / (Number(cn.x) - Number(cp.x));
							// Calculate the intersection point
							var intersecX = ((cp.y - (cp.x * raise)) - (cd.y - (cd.x * (-Math.pow(raise, -1))))) / ((-Math.pow(raise, -1)) - raise);
							var intersecY = (cp.y - (cp.x * raise)) + (raise * intersecX);

							if (isNaN(intersecX) || isNaN(intersecY)) { return; }

							this.docker.bounds.centerMoveTo(intersecX, intersecY);
						}
					}

				}
			}
		}
		//this.facade.getCanvas().update();
		this.dockerParent._update();
	},

	/**
	 * Docker MouseUp Handler
	 *
	 */
	dockerMovedFinished: function (event) {

		/* Reset to buffered shape selection */
		this.facade.setSelection(this.shapeSelection);

		// Hide the border
		this.hideHighlight();

		// Show all Labels from Docker
		this.dockerParent.getLabels().each(function (label) {
			label.show();
			//label.update();
		});

		// If there is a last top level Shape
		if (this.lastUIObj && (this.isStartDocker || this.isEndDocker)) {
			// If there is a valid connection, the set as a docked Shape to them
			if (this.isValid) {

				this.docker.setDockedShape(this.lastUIObj);

				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_DRAGDOCKER_DOCKED,
					docker: this.docker,
					parent: this.docker.parent,
					target: this.lastUIObj
				});
			}

			this.hideMagnets(this.lastUIObj)
		}

		// Hide the Docker
		this.docker.hide();

		if (this.outerDockerNotMoved) {
			// Get the EventPosition and all Shapes on these point
			var evPos = this.facade.eventCoordinates(event);
			var shapes = this.facade.getCanvas().getAbstractShapesAtPosition(evPos);

			/* Remove edges from selection */
			var shapeWithoutEdges = shapes.findAll(function (node) {
				return node instanceof ORYX.Core.Node;
			});
			shapes = shapeWithoutEdges.length ? shapeWithoutEdges : shapes;
			this.facade.setSelection(shapes);
		} else {
			//Command-Pattern for dragging one docker
			var dragDockerCommand = ORYX.Core.Command.extend({
				construct: function (docker, newPos, oldPos, newDockedShape, oldDockedShape, facade) {
					this.docker = docker;
					this.index = docker.parent.dockers.indexOf(docker);
					this.newPosition = newPos;
					this.newDockedShape = newDockedShape;
					this.oldPosition = oldPos;
					this.oldDockedShape = oldDockedShape;
					this.facade = facade;
					this.index = docker.parent.dockers.indexOf(docker);
					this.shape = docker.parent;

				},
				execute: function () {
					if (!this.docker.parent) {
						this.docker = this.shape.dockers[this.index];
					}
					this.dock(this.newDockedShape, this.newPosition);
					this.removedDockers = this.shape.removeUnusedDockers();
					this.facade.updateSelection();
				},
				rollback: function () {
					this.dock(this.oldDockedShape, this.oldPosition);
					(this.removedDockers || $H({})).each(function (d) {
						this.shape.add(d.value, Number(d.key));
						this.shape._update(true);
					}.bind(this))
					this.facade.updateSelection();
				},
				dock: function (toDockShape, pos) {
					// Set the Docker to the new Shape
					this.docker.setDockedShape(undefined);
					if (toDockShape) {
						this.docker.setDockedShape(toDockShape);
						this.docker.setReferencePoint(pos);
						//this.docker.update();	
						//this.docker.parent._update();				
					} else {
						this.docker.bounds.centerMoveTo(pos);
					}

					this.facade.getCanvas().update();



				}
			});


			if (this.docker.parent) {
				// Instanziate the dockCommand
				var command = new dragDockerCommand(this.docker, this.docker.getDockedShape() ? this.docker.referencePoint : this.docker.bounds.center(), this._commandArg.refPoint, this.docker.getDockedShape(), this._commandArg.dockedShape, this.facade);
				this.facade.executeCommands([command]);
			}
		}





		// Update all Shapes
		//this.facade.updateSelection();

		// Undefined all variables
		this.docker = undefined;
		this.dockerParent = undefined;
		this.dockerSource = undefined;
		this.dockerTarget = undefined;
		this.lastUIObj = undefined;
	},

	/**
	 * Hide the highlighting
	 */
	hideHighlight: function () {
		this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'validDockedShape' });
	},

	/**
	 * Show the highlighting
	 *
	 */
	showHighlight: function (uiObj, color) {

		this.facade.raiseEvent({
			type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
			highlightId: 'validDockedShape',
			elements: [uiObj],
			color: color
		});
	},

	showMagnets: function (uiObj) {
		uiObj.magnets.each(function (magnet) {
			magnet.show();
		});
	},

	hideMagnets: function (uiObj) {
		uiObj.magnets.each(function (magnet) {
			magnet.hide();
		});
	},

	getHighestParentBeforeCanvas: function (shape) {
		if (!(shape instanceof ORYX.Core.Shape)) { return undefined; }

		var parent = shape.parent;
		while (parent && !(parent.parent instanceof ORYX.Core.Canvas)) {
			parent = parent.parent;
		}

		return parent;
	}

});

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.AddDocker = Clazz.extend({

	/**
	 *	Constructor
	 *	@param {Object} Facade: The Facade of the Editor
	 */
	construct: function (facade) {
		this.facade = facade;

		this.facade.offer({
			'name': ORYX.I18N.AddDocker.add,
			'functionality': this.enableAddDocker.bind(this),
			'group': ORYX.I18N.AddDocker.group,
			'icon': ORYX.PATH + "/images/vector_add.png",
			'description': ORYX.I18N.AddDocker.addDesc,
			'index': 1,
			'toggle': true,
			'minShape': 0,
			'maxShape': 0
		});


		this.facade.offer({
			'name': ORYX.I18N.AddDocker.del,
			'functionality': this.enableDeleteDocker.bind(this),
			'group': ORYX.I18N.AddDocker.group,
			'icon': ORYX.PATH + "/images/vector_delete.png",
			'description': ORYX.I18N.AddDocker.delDesc,
			'index': 2,
			'toggle': true,
			'minShape': 0,
			'maxShape': 0
		});

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEDOWN, this.handleMouseDown.bind(this));
	},

	enableAddDocker: function (button, pressed) {
		//FIXME This should be done while construct, but this isn't possible right now!
		this.addDockerButton = button;

		// Unpress deleteDockerButton
		if (pressed && this.deleteDockerButton)
			this.deleteDockerButton.toggle(false);
	},
	enableDeleteDocker: function (button, pressed) {
		//FIXME This should be done while construct, but this isn't possible right now!
		this.deleteDockerButton = button;

		// Unpress addDockerButton
		if (pressed && this.addDockerButton)
			this.addDockerButton.toggle(false);
	},

	enabledAdd: function () {
		return this.addDockerButton ? this.addDockerButton.pressed : false;
	},
	enabledDelete: function () {
		return this.deleteDockerButton ? this.deleteDockerButton.pressed : false;
	},

	/**
	 * MouseDown Handler
	 *
	 */
	handleMouseDown: function (event, uiObj) {
		if (this.enabledAdd() && uiObj instanceof ORYX.Core.Edge) {
			this.newDockerCommand({
				edge: uiObj,
				position: this.facade.eventCoordinates(event)
			});
		} else if (this.enabledDelete() &&
			uiObj instanceof ORYX.Core.Controls.Docker &&
			uiObj.parent instanceof ORYX.Core.Edge) {
			this.newDockerCommand({
				edge: uiObj.parent,
				docker: uiObj
			});
		} else if (this.enabledAdd()) {
			this.addDockerButton.toggle(false);
		} else if (this.enabledDelete()) {
			this.deleteDockerButton.toggle(false);
		}
	},

	// Options: edge (required), position (required if add), docker (required if delete)
	newDockerCommand: function (options) {
		if (!options.edge)
			return;

		var commandClass = ORYX.Core.Command.extend({
			construct: function (addEnabled, deleteEnabled, edge, docker, pos, facade) {
				this.addEnabled = addEnabled;
				this.deleteEnabled = deleteEnabled;
				this.edge = edge;
				this.docker = docker;
				this.pos = pos;
				this.facade = facade;
				//this.index = docker.parent.dockers.indexOf(docker);
			},
			execute: function () {
				if (this.addEnabled) {
					if (!this.docker) {
						this.docker = this.edge.addDocker(this.pos);
						this.index = this.edge.dockers.indexOf(this.docker);
					} else {
						this.edge.add(this.docker, this.index);
					}
				}
				else if (this.deleteEnabled) {
					this.index = this.edge.dockers.indexOf(this.docker);
					this.pos = this.docker.bounds.center();
					this.edge.removeDocker(this.docker);
				}
				this.edge.getLabels().invoke("show");
				this.facade.getCanvas().update();
				this.facade.updateSelection();
			},
			rollback: function () {
				if (this.addEnabled) {
					if (this.docker instanceof ORYX.Core.Controls.Docker) {
						this.edge.removeDocker(this.docker);
					}
				}
				else if (this.deleteEnabled) {
					this.edge.add(this.docker, this.index);
				}
				this.edge.getLabels().invoke("show");
				this.facade.getCanvas().update();
				this.facade.updateSelection();
			}
		})

		var command = new commandClass(this.enabledAdd(), this.enabledDelete(), options.edge, options.docker, options.position, this.facade);

		this.facade.executeCommands([command]);
	}
});

/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.SelectionFrame = Clazz.extend({

	construct: function (facade) {
		this.facade = facade;

		// Register on MouseEvents
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_MOUSEDOWN, this.handleMouseDown.bind(this));
		document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEUP, this.handleMouseUp.bind(this), true);

		// Some initiale variables
		this.position = { x: 0, y: 0 };
		this.size = { width: 0, height: 0 };
		this.offsetPosition = { x: 0, y: 0 }

		// (Un)Register Mouse-Move Event
		this.moveCallback = undefined;
		this.offsetScroll = { x: 0, y: 0 }
		// HTML-Node of Selection-Frame
		this.node = ORYX.Editor.graft("http://www.w3.org/1999/xhtml", this.facade.getCanvas().getHTMLContainer(),
			['div', { 'class': 'Oryx_SelectionFrame' }]);

		this.hide();
	},

	handleMouseDown: function (event, uiObj) {
		// If there is the Canvas
		if (uiObj instanceof ORYX.Core.Canvas) {
			// Calculate the Offset
			var scrollNode = uiObj.rootNode.parentNode.parentNode;

			var a = this.facade.getCanvas().node.getScreenCTM();
			this.offsetPosition = {
				x: a.e,
				y: a.f
			}

			// Set the new Position
			this.setPos({ x: Event.pointerX(event) - this.offsetPosition.x, y: Event.pointerY(event) - this.offsetPosition.y });
			// Reset the size
			this.resize({ width: 0, height: 0 });
			this.moveCallback = this.handleMouseMove.bind(this);

			// Register Mouse-Move Event
			document.documentElement.addEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this.moveCallback, false);

			this.offsetScroll = { x: scrollNode.scrollLeft, y: scrollNode.scrollTop };

			// Show the Frame
			this.show();



		}

		Event.stop(event);
	},

	handleMouseUp: function (event) {
		// If there was an MouseMoving
		if (this.moveCallback) {
			// Hide the Frame
			this.hide();

			// Unregister Mouse-Move
			document.documentElement.removeEventListener(ORYX.CONFIG.EVENT_MOUSEMOVE, this.moveCallback, false);

			this.moveCallback = undefined;

			var corrSVG = this.facade.getCanvas().node.getScreenCTM();

			// Calculate the positions of the Frame
			var a = {
				x: this.size.width > 0 ? this.position.x : this.position.x + this.size.width,
				y: this.size.height > 0 ? this.position.y : this.position.y + this.size.height
			}

			var b = {
				x: a.x + Math.abs(this.size.width),
				y: a.y + Math.abs(this.size.height)
			}

			// Fit to SVG-Coordinates
			a.x /= corrSVG.a; a.y /= corrSVG.d;
			b.x /= corrSVG.a; b.y /= corrSVG.d;


			// Calculate the elements from the childs of the canvas
			var elements = this.facade.getCanvas().getChildShapes(true).findAll(function (value) {
				var absBounds = value.absoluteBounds();
				var bA = absBounds.upperLeft();
				var bB = absBounds.lowerRight();
				if (bA.x > a.x && bA.y > a.y && bB.x < b.x && bB.y < b.y)
					return true;
				return false
			});

			// Set the selection
			this.facade.setSelection(elements);
		}
	},

	handleMouseMove: function (event) {
		// Calculate the size
		var size = {
			width: Event.pointerX(event) - this.position.x - this.offsetPosition.x,
			height: Event.pointerY(event) - this.position.y - this.offsetPosition.y,
		}

		var scrollNode = this.facade.getCanvas().rootNode.parentNode.parentNode;
		size.width -= this.offsetScroll.x - scrollNode.scrollLeft;
		size.height -= this.offsetScroll.y - scrollNode.scrollTop;

		// Set the size
		this.resize(size);

		Event.stop(event);
	},

	hide: function () {
		this.node.style.display = "none";
	},

	show: function () {
		this.node.style.display = "";
	},

	setPos: function (pos) {
		// Set the Position
		this.node.style.top = pos.y + "px";
		this.node.style.left = pos.x + "px";
		this.position = pos;
	},

	resize: function (size) {

		// Calculate the negative offset
		this.setPos(this.position);
		this.size = Object.clone(size);

		if (size.width < 0) {
			this.node.style.left = (this.position.x + size.width) + "px";
			size.width = - size.width;
		}
		if (size.height < 0) {
			this.node.style.top = (this.position.y + size.height) + "px";
			size.height = - size.height;
		}

		// Set the size
		this.node.style.width = size.width + "px";
		this.node.style.height = size.height + "px";
	}

});


/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.ShapeHighlighting = Clazz.extend({

	construct: function (facade) {

		this.parentNode = facade.getCanvas().getSvgContainer();

		// The parent Node
		this.node = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.parentNode,
			['g']);

		this.highlightNodes = {};

		facade.registerOnEvent(ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW, this.setHighlight.bind(this));
		facade.registerOnEvent(ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, this.hideHighlight.bind(this));

	},

	setHighlight: function (options) {
		if (options && options.highlightId) {
			var node = this.highlightNodes[options.highlightId];

			if (!node) {
				node = ORYX.Editor.graft("http://www.w3.org/2000/svg", this.node,
					['path', {
						"stroke-width": 2.0, "fill": "none"
					}]);

				this.highlightNodes[options.highlightId] = node;
			}

			if (options.elements && options.elements.length > 0) {

				this.setAttributesByStyle(node, options);
				this.show(node);

			} else {

				this.hide(node);

			}

		}
	},

	hideHighlight: function (options) {
		if (options && options.highlightId && this.highlightNodes[options.highlightId]) {
			this.hide(this.highlightNodes[options.highlightId]);
		}
	},

	hide: function (node) {
		node.setAttributeNS(null, 'display', 'none');
	},

	show: function (node) {
		node.setAttributeNS(null, 'display', '');
	},

	setAttributesByStyle: function (node, options) {

		// If the style say, that it should look like a rectangle
		if (options.style && options.style == ORYX.CONFIG.SELECTION_HIGHLIGHT_STYLE_RECTANGLE) {

			// Set like this
			var bo = options.elements[0].absoluteBounds();

			var strWidth = options.strokewidth ? options.strokewidth : ORYX.CONFIG.BORDER_OFFSET

			node.setAttributeNS(null, "d", this.getPathRectangle(bo.a, bo.b, strWidth));
			node.setAttributeNS(null, "stroke", options.color ? options.color : ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR);
			node.setAttributeNS(null, "stroke-opacity", options.opacity ? options.opacity : 0.2);
			node.setAttributeNS(null, "stroke-width", strWidth);

		} else if (options.elements.length == 1
			&& options.elements[0] instanceof ORYX.Core.Edge &&
			options.highlightId != "selection") {

			/* Highlight containment of edge's childs */
			node.setAttributeNS(null, "d", this.getPathEdge(options.elements[0].dockers));
			node.setAttributeNS(null, "stroke", options.color ? options.color : ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR);
			node.setAttributeNS(null, "stroke-opacity", options.opacity ? options.opacity : 0.2);
			node.setAttributeNS(null, "stroke-width", ORYX.CONFIG.OFFSET_EDGE_BOUNDS);

		} else {
			// If not, set just the corners
			node.setAttributeNS(null, "d", this.getPathByElements(options.elements));
			node.setAttributeNS(null, "stroke", options.color ? options.color : ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR);
			node.setAttributeNS(null, "stroke-opacity", options.opacity ? options.opacity : 1.0);
			node.setAttributeNS(null, "stroke-width", options.strokewidth ? options.strokewidth : 2.0);

		}
	},

	getPathByElements: function (elements) {
		if (!elements || elements.length <= 0) { return undefined }

		// Get the padding and the size
		var padding = ORYX.CONFIG.SELECTED_AREA_PADDING;

		var path = ""

		// Get thru all Elements
		elements.each((function (element) {
			if (!element) { return }
			// Get the absolute Bounds and the two Points
			var bounds = element.absoluteBounds();
			bounds.widen(padding)
			var a = bounds.upperLeft();
			var b = bounds.lowerRight();

			path = path + this.getPath(a, b);

		}).bind(this));

		return path;

	},

	getPath: function (a, b) {

		return this.getPathCorners(a, b);

	},

	getPathCorners: function (a, b) {

		var size = ORYX.CONFIG.SELECTION_HIGHLIGHT_SIZE;

		var path = ""

		// Set: Upper left 
		path = path + "M" + a.x + " " + (a.y + size) + " l0 -" + size + " l" + size + " 0 ";
		// Set: Lower left
		path = path + "M" + a.x + " " + (b.y - size) + " l0 " + size + " l" + size + " 0 ";
		// Set: Lower right
		path = path + "M" + b.x + " " + (b.y - size) + " l0 " + size + " l-" + size + " 0 ";
		// Set: Upper right
		path = path + "M" + b.x + " " + (a.y + size) + " l0 -" + size + " l-" + size + " 0 ";

		return path;
	},

	getPathRectangle: function (a, b, strokeWidth) {

		var size = ORYX.CONFIG.SELECTION_HIGHLIGHT_SIZE;

		var path = ""
		var offset = strokeWidth / 2.0;

		// Set: Upper left 
		path = path + "M" + (a.x + offset) + " " + (a.y);
		path = path + " L" + (a.x + offset) + " " + (b.y - offset);
		path = path + " L" + (b.x - offset) + " " + (b.y - offset);
		path = path + " L" + (b.x - offset) + " " + (a.y + offset);
		path = path + " L" + (a.x + offset) + " " + (a.y + offset);

		return path;
	},

	getPathEdge: function (edgeDockers) {
		var length = edgeDockers.length;
		var path = "M" + edgeDockers[0].bounds.center().x + " "
			+ edgeDockers[0].bounds.center().y;

		for (i = 1; i < length; i++) {
			var dockerPoint = edgeDockers[i].bounds.center();
			path = path + " L" + dockerPoint.x + " " + dockerPoint.y;
		}

		return path;
	}

});


ORYX.Plugins.HighlightingSelectedShapes = Clazz.extend({

	construct: function (facade) {
		this.facade = facade;
		this.opacityFull = 0.9;
		this.opacityLow = 0.4;

		// Register on Dragging-Events for show/hide of ShapeMenu
		//this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDROP_START, this.hide.bind(this));
		//this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDROP_END,  this.show.bind(this));		
	},

	/**
	 * On the Selection-Changed
	 *
	 */
	onSelectionChanged: function (event) {
		if (event.elements && event.elements.length > 1) {
			this.facade.raiseEvent({
				type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
				highlightId: 'selection',
				elements: event.elements.without(event.subSelection),
				color: ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR,
				opacity: !event.subSelection ? this.opacityFull : this.opacityLow
			});

			if (event.subSelection) {
				this.facade.raiseEvent({
					type: ORYX.CONFIG.EVENT_HIGHLIGHT_SHOW,
					highlightId: 'subselection',
					elements: [event.subSelection],
					color: ORYX.CONFIG.SELECTION_HIGHLIGHT_COLOR,
					opacity: this.opacityFull
				});
			} else {
				this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'subselection' });
			}

		} else {
			this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'selection' });
			this.facade.raiseEvent({ type: ORYX.CONFIG.EVENT_HIGHLIGHT_HIDE, highlightId: 'subselection' });
		}
	}
});/**
 * Copyright (c) 2008
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * HOW to USE the OVERLAY PLUGIN:
 * 	You can use it via the event mechanism from the editor
 * 	by using facade.raiseEvent( <option> )
 * 
 * 	As an example please have a look in the overlayexample.js
 * 
 * 	The option object should/have to have following attributes:
 * 
 * 	Key				Value-Type							Description
 * 	================================================================
 * 
 *	type 			ORYX.CONFIG.EVENT_OVERLAY_SHOW | ORYX.CONFIG.EVENT_OVERLAY_HIDE		This is the type of the event	
 *	id				<String>							You have to use an unified id for later on hiding this overlay
 *	shapes 			<ORYX.Core.Shape[]>					The Shapes where the attributes should be changed
 *	attributes 		<Object>							An object with svg-style attributes as key-value pair
 *	node			<SVGElement>						An SVG-Element could be specified for adding this to the Shape
 *	nodePosition	"N"|"NE"|"E"|"SE"|"S"|"SW"|"W"|"NW"|"START"|"END"	The position for the SVG-Element relative to the 
 *														specified Shape. "START" and "END" are just using for a Edges, then
 *														the relation is the start or ending Docker of this edge.
 *	
 * 
 **/
if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.Overlay = Clazz.extend({

	facade: undefined,

	styleNode: undefined,

	construct: function (facade) {

		this.facade = facade;

		this.changes = [];

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_OVERLAY_SHOW, this.show.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_OVERLAY_HIDE, this.hide.bind(this));

		this.styleNode = document.createElement('style')
		this.styleNode.setAttributeNS(null, 'type', 'text/css')

		document.getElementsByTagName('head')[0].appendChild(this.styleNode)

	},

	/**
	 * Show the overlay for specific nodes
	 * @param {Object} options
	 * 
	 * 	String				options.id		- MUST - Define the id of the overlay (is needed for the hiding of this overlay)		
	 *	ORYX.Core.Shape[] 	options.shapes 	- MUST - Define the Shapes for the changes
	 * 	attr-name:value		options.changes	- Defines all the changes which should be shown
	 * 
	 * 
	 */
	show: function (options) {

		// Checks if all arguments are available
		if (!options ||
			!options.shapes || !options.shapes instanceof Array ||
			!options.id || !options.id instanceof String || options.id.length == 0) {

			return

		}

		//if( this.changes[options.id]){
		//	this.hide( options )
		//}


		// Checked if attributes are setted
		if (options.attributes) {

			// FOR EACH - Shape
			options.shapes.each(function (el) {

				// Checks if the node is a Shape
				if (!el instanceof ORYX.Core.Shape) { return }

				this.setAttributes(el.node, options.attributes)

			}.bind(this))

		}

		var isSVG = true
		try {
			isSVG = options.node && options.node instanceof SVGElement;
		} catch (e) { }

		// Checks if node is setted and if this is an SVGElement		
		if (options.node && isSVG) {

			options["_temps"] = []

			// FOR EACH - Node
			options.shapes.each(function (el, index) {

				// Checks if the node is a Shape
				if (!el instanceof ORYX.Core.Shape) { return }

				var _temp = {}
				_temp.svg = options.dontCloneNode ? options.node : options.node.cloneNode(true);

				// Add the svg node to the ORYX-Shape
				el.node.firstChild.appendChild(_temp.svg)

				// If
				if (el instanceof ORYX.Core.Edge && !options.nodePosition) {
					options['nodePosition'] = "START"
				}

				// If the node position is setted, it has to be transformed
				if (options.nodePosition) {

					var b = el.bounds;
					var p = options.nodePosition.toUpperCase();

					// Check the values of START and END
					if (el instanceof ORYX.Core.Node && p == "START") {
						p = "NW";
					} else if (el instanceof ORYX.Core.Node && p == "END") {
						p = "SE";
					} else if (el instanceof ORYX.Core.Edge && p == "START") {
						b = el.getDockers().first().bounds
					} else if (el instanceof ORYX.Core.Edge && p == "END") {
						b = el.getDockers().last().bounds
					}

					// Create a callback for the changing the position 
					// depending on the position string
					_temp.callback = function () {

						var x = 0; var y = 0;

						if (p == "NW") {
							// Do Nothing
						} else if (p == "N") {
							x = b.width() / 2;
						} else if (p == "NE") {
							x = b.width();
						} else if (p == "E") {
							x = b.width(); y = b.height() / 2;
						} else if (p == "SE") {
							x = b.width(); y = b.height();
						} else if (p == "S") {
							x = b.width() / 2; y = b.height();
						} else if (p == "SW") {
							y = b.height();
						} else if (p == "W") {
							y = b.height() / 2;
						} else if (p == "START" || p == "END") {
							x = b.width() / 2; y = b.height() / 2;
						} else {
							return
						}

						if (el instanceof ORYX.Core.Edge) {
							x += b.upperLeft().x; y += b.upperLeft().y;
						}

						_temp.svg.setAttributeNS(null, "transform", "translate(" + x + ", " + y + ")")

					}.bind(this)

					_temp.element = el;
					_temp.callback();

					b.registerCallback(_temp.callback);

				}


				options._temps.push(_temp)

			}.bind(this))



		}


		// Store the changes
		if (!this.changes[options.id]) {
			this.changes[options.id] = [];
		}

		this.changes[options.id].push(options);

	},

	/**
	 * Hide the overlay with the spefic id
	 * @param {Object} options
	 */
	hide: function (options) {

		// Checks if all arguments are available
		if (!options ||
			!options.id || !options.id instanceof String || options.id.length == 0 ||
			!this.changes[options.id]) {

			return

		}


		// Delete all added attributes
		// FOR EACH - Shape
		this.changes[options.id].each(function (option) {

			option.shapes.each(function (el, index) {

				// Checks if the node is a Shape
				if (!el instanceof ORYX.Core.Shape) { return }

				this.deleteAttributes(el.node)

			}.bind(this));


			if (option._temps) {

				option._temps.each(function (tmp) {
					// Delete the added Node, if there is one
					if (tmp.svg && tmp.svg.parentNode) {
						tmp.svg.parentNode.removeChild(tmp.svg)
					}

					// If 
					if (tmp.callback && tmp.element) {
						// It has to be unregistered from the edge
						tmp.element.bounds.unregisterCallback(tmp.callback)
					}

				}.bind(this))

			}


		}.bind(this));


		this.changes[options.id] = null;


	},


	/**
	 * Set the given css attributes to that node
	 * @param {HTMLElement} node
	 * @param {Object} attributes
	 */
	setAttributes: function (node, attributes) {


		// Get all the childs from ME
		var childs = this.getAllChilds(node.firstChild.firstChild)

		var ids = []

		// Add all Attributes which have relation to another node in this document and concate the pure id out of it
		// This is for example important for the markers of a edge
		childs.each(function (e) { ids.push($A(e.attributes).findAll(function (attr) { return attr.nodeValue.startsWith('url(#') })) })
		ids = ids.flatten().compact();
		ids = ids.collect(function (s) { return s.nodeValue }).uniq();
		ids = ids.collect(function (s) { return s.slice(5, s.length - 1) })

		// Add the node ID to the id
		ids.unshift(node.id + ' .me')

		var attr = $H(attributes);
		var attrValue = attr.toJSON().gsub(',', ';').gsub('"', '');
		var attrMarkerValue = attributes.stroke ? attrValue.slice(0, attrValue.length - 1) + "; fill:" + attributes.stroke + ";}" : attrValue;
		var attrTextValue;
		if (attributes.fill) {
			var copyAttr = Object.clone(attributes);
			copyAttr.fill = "black";
			attrTextValue = $H(copyAttr).toJSON().gsub(',', ';').gsub('"', '');
		}

		// Create the CSS-Tags Style out of the ids and the attributes
		csstags = ids.collect(function (s, i) { return "#" + s + " * " + (!i ? attrValue : attrMarkerValue) + "" + (attrTextValue ? " #" + s + " text * " + attrTextValue : "") })

		// Join all the tags
		var s = csstags.join(" ") + "\n"

		// And add to the end of the style tag
		this.styleNode.appendChild(document.createTextNode(s));


	},

	/**
	 * Deletes all attributes which are
	 * added in a special style sheet for that node
	 * @param {HTMLElement} node 
	 */
	deleteAttributes: function (node) {

		// Get all children which contains the node id		
		var delEl = $A(this.styleNode.childNodes)
			.findAll(function (e) { return e.textContent.include('#' + node.id) });

		// Remove all of them
		delEl.each(function (el) {
			el.parentNode.removeChild(el);
		});
	},

	getAllChilds: function (node) {

		var childs = $A(node.childNodes)

		$A(node.childNodes).each(function (e) {
			childs.push(this.getAllChilds(e))
		}.bind(this))

		return childs.flatten();
	}


});
/**
 * Copyright (c) 2006
 * Martin Czuchra, Nicolas Peters, Daniel Polak, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.Edit = Clazz.extend({

	construct: function (facade) {

		this.facade = facade;
		this.clipboard = new ORYX.Plugins.Edit.ClipBoard();

		//this.facade.registerOnEvent(ORYX.CONFIG.EVENT_KEYDOWN, this.keyHandler.bind(this));

		this.facade.offer({
			name: ORYX.I18N.Edit.cut,
			description: ORYX.I18N.Edit.cutDesc,
			icon: ORYX.PATH + "/images/cut.png",
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 88,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.callEdit.bind(this, this.editCut),
			group: ORYX.I18N.Edit.group,
			index: 1,
			minShape: 1
		});

		this.facade.offer({
			name: ORYX.I18N.Edit.copy,
			description: ORYX.I18N.Edit.copyDesc,
			icon: ORYX.PATH + "/images/page_copy.png",
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 67,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.callEdit.bind(this, this.editCopy, [true, false]),
			group: ORYX.I18N.Edit.group,
			index: 2,
			minShape: 1
		});

		this.facade.offer({
			name: ORYX.I18N.Edit.paste,
			description: ORYX.I18N.Edit.pasteDesc,
			icon: ORYX.PATH + "/images/page_paste.png",
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 86,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.callEdit.bind(this, this.editPaste),
			isEnabled: this.clipboard.isOccupied.bind(this.clipboard),
			group: ORYX.I18N.Edit.group,
			index: 3,
			minShape: 0,
			maxShape: 0
		});

		this.facade.offer({
			name: ORYX.I18N.Edit.del,
			description: ORYX.I18N.Edit.delDesc,
			icon: ORYX.PATH + "/images/cross.png",
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 8,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			},
			{
				keyCode: 46,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.callEdit.bind(this, this.editDelete),
			group: ORYX.I18N.Edit.group,
			index: 4,
			minShape: 1
		});
	},

	callEdit: function (fn, args) {
		window.setTimeout(function () {
			fn.apply(this, (args instanceof Array ? args : []));
		}.bind(this), 1);
	},

	/**
	 * Handles the mouse down event and starts the copy-move-paste action, if
	 * control or meta key is pressed.
	 */
	handleMouseDown: function (event) {
		if (this._controlPressed) {
			this._controlPressed = false;
			this.editCopy();

			this.editPaste();
			event.forceExecution = true;
			this.facade.raiseEvent(event, this.clipboard.shapesAsJson);

		}
	},



    /**
     * Returns a list of shapes which should be considered while copying.
     * Besides the shapes of given ones, edges and attached nodes are added to the result set.
     * If one of the given shape is a child of another given shape, it is not put into the result. 
     */
	getAllShapesToConsider: function (shapes) {
		var shapesToConsider = []; // only top-level shapes
		var childShapesToConsider = []; // all child shapes of top-level shapes

		shapes.each(function (shape) {
			//Throw away these shapes which have a parent in given shapes
			isChildShapeOfAnother = shapes.any(function (s2) {
				return s2.hasChildShape(shape);
			});
			if (isChildShapeOfAnother) return;

			// This shape should be considered
			shapesToConsider.push(shape);
			// Consider attached nodes (e.g. intermediate events)
			if (shape instanceof ORYX.Core.Node) {
				var attached = shape.getOutgoingNodes();
				attached = attached.findAll(function (a) { return !shapes.include(a) });
				shapesToConsider = shapesToConsider.concat(attached);
			}

			childShapesToConsider = childShapesToConsider.concat(shape.getChildShapes(true));
		}.bind(this));

		// All edges between considered child shapes should be considered
		// Look for these edges having incoming and outgoing in childShapesToConsider
		var edgesToConsider = this.facade.getCanvas().getChildEdges().select(function (edge) {
			// Ignore if already added
			if (shapesToConsider.include(edge)) return false;
			// Ignore if there are no docked shapes
			if (edge.getAllDockedShapes().size() === 0) return false;
			// True if all docked shapes are in considered child shapes
			return edge.getAllDockedShapes().all(function (shape) {
				// Remember: Edges can have other edges on outgoing, that is why edges must not be included in childShapesToConsider
				return shape instanceof ORYX.Core.Edge || childShapesToConsider.include(shape);
			});
		});
		shapesToConsider = shapesToConsider.concat(edgesToConsider);

		return shapesToConsider;
	},

    /**
     * Performs the cut operation by first copy-ing and then deleting the
     * current selection.
     */
	editCut: function () {
		//TODO document why this returns false.
		//TODO document what the magic boolean parameters are supposed to do.

		this.editCopy(false, true);
		this.editDelete(true);
		return false;
	},

    /**
     * Performs the copy operation.
     * @param {Object} will_not_update ??
     */
	editCopy: function (will_update, useNoOffset) {
		var selection = this.facade.getSelection();

		//if the selection is empty, do not remove the previously copied elements
		if (selection.length == 0) return;

		this.clipboard.refresh(selection, this.getAllShapesToConsider(selection), this.facade.getCanvas().getStencil().stencilSet().namespace(), useNoOffset);

		if (will_update) this.facade.updateSelection();
	},

    /**
     * Performs the paste operation.
     */
	editPaste: function () {
		// Create a new canvas with childShapes 
		//and stencilset namespace to be JSON Import conform
		var canvas = {
			childShapes: this.clipboard.shapesAsJson,
			stencilset: {
				namespace: this.clipboard.SSnamespace
			}
		}
		// Apply json helper to iterate over json object
		Ext.apply(canvas, ORYX.Core.AbstractShape.JSONHelper);

		var childShapeResourceIds = canvas.getChildShapes(true).pluck("resourceId");
		var outgoings = {};
		// Iterate over all shapes
		canvas.eachChild(function (shape, parent) {
			// Throw away these references where referenced shape isn't copied
			shape.outgoing = shape.outgoing.select(function (out) {
				return childShapeResourceIds.include(out.resourceId);
			});
			shape.outgoing.each(function (out) {
				if (!outgoings[out.resourceId]) { outgoings[out.resourceId] = [] }
				outgoings[out.resourceId].push(shape)
			});

			return shape;
		}.bind(this), true, true);


		// Iterate over all shapes
		canvas.eachChild(function (shape, parent) {

			// Check if there has a valid target
			if (shape.target && !(childShapeResourceIds.include(shape.target.resourceId))) {
				shape.target = undefined;
				shape.targetRemoved = true;
			}

			// Check if the first docker is removed
			if (shape.dockers &&
				shape.dockers.length >= 1 &&
				shape.dockers[0].getDocker &&
				((shape.dockers[0].getDocker().getDockedShape() &&
					!childShapeResourceIds.include(shape.dockers[0].getDocker().getDockedShape().resourceId)) ||
					!shape.getShape().dockers[0].getDockedShape() && !outgoings[shape.resourceId])) {

				shape.sourceRemoved = true;
			}

			return shape;
		}.bind(this), true, true);


		// Iterate over top-level shapes
		canvas.eachChild(function (shape, parent) {
			// All top-level shapes should get an offset in their bounds
			// Move the shape occording to COPY_MOVE_OFFSET
			if (this.clipboard.useOffset) {
				shape.bounds = {
					lowerRight: {
						x: shape.bounds.lowerRight.x + ORYX.CONFIG.COPY_MOVE_OFFSET,
						y: shape.bounds.lowerRight.y + ORYX.CONFIG.COPY_MOVE_OFFSET
					},
					upperLeft: {
						x: shape.bounds.upperLeft.x + ORYX.CONFIG.COPY_MOVE_OFFSET,
						y: shape.bounds.upperLeft.y + ORYX.CONFIG.COPY_MOVE_OFFSET
					}
				};
			}
			// Only apply offset to shapes with a target
			if (shape.dockers) {
				shape.dockers = shape.dockers.map(function (docker, i) {
					// If shape had a target but the copied does not have anyone anymore,
					// migrate the relative dockers to absolute ones.
					if ((shape.targetRemoved === true && i == shape.dockers.length - 1 && docker.getDocker) ||
						(shape.sourceRemoved === true && i == 0 && docker.getDocker)) {

						docker = docker.getDocker().bounds.center();
					}

					// If it is the first docker and it has a docked shape, 
					// just return the coordinates
					if ((i == 0 && docker.getDocker instanceof Function &&
						shape.sourceRemoved !== true && (docker.getDocker().getDockedShape() || ((outgoings[shape.resourceId] || []).length > 0 && (!(shape.getShape() instanceof ORYX.Core.Node) || outgoings[shape.resourceId][0].getShape() instanceof ORYX.Core.Node)))) ||
						(i == shape.dockers.length - 1 && docker.getDocker instanceof Function &&
							shape.targetRemoved !== true && (docker.getDocker().getDockedShape() || shape.target))) {

						return {
							x: docker.x,
							y: docker.y,
							getDocker: docker.getDocker
						}
					} else if (this.clipboard.useOffset) {
						return {
							x: docker.x + ORYX.CONFIG.COPY_MOVE_OFFSET,
							y: docker.y + ORYX.CONFIG.COPY_MOVE_OFFSET,
							getDocker: docker.getDocker
						};
					} else {
						return {
							x: docker.x,
							y: docker.y,
							getDocker: docker.getDocker
						};
					}
				}.bind(this));

			} else if (shape.getShape() instanceof ORYX.Core.Node && shape.dockers && shape.dockers.length > 0 && (!shape.dockers.first().getDocker || shape.sourceRemoved === true || !(shape.dockers.first().getDocker().getDockedShape() || outgoings[shape.resourceId]))) {

				shape.dockers = shape.dockers.map(function (docker, i) {

					if ((shape.sourceRemoved === true && i == 0 && docker.getDocker)) {
						docker = docker.getDocker().bounds.center();
					}

					if (this.clipboard.useOffset) {
						return {
							x: docker.x + ORYX.CONFIG.COPY_MOVE_OFFSET,
							y: docker.y + ORYX.CONFIG.COPY_MOVE_OFFSET,
							getDocker: docker.getDocker
						};
					} else {
						return {
							x: docker.x,
							y: docker.y,
							getDocker: docker.getDocker
						};
					}
				}.bind(this));
			}

			return shape;
		}.bind(this), false, true);

		this.clipboard.useOffset = true;
		this.facade.importJSON(canvas);
	},

    /**
     * Performs the delete operation. No more asking.
     */
	editDelete: function () {
		var selection = this.facade.getSelection();

		var clipboard = new ORYX.Plugins.Edit.ClipBoard();
		clipboard.refresh(selection, this.getAllShapesToConsider(selection));

		var command = new ORYX.Plugins.Edit.DeleteCommand(clipboard, this.facade);

		this.facade.executeCommands([command]);
	}
});

ORYX.Plugins.Edit.ClipBoard = Clazz.extend({
	construct: function () {
		this.shapesAsJson = [];
		this.selection = [];
		this.SSnamespace = "";
		this.useOffset = true;
	},
	isOccupied: function () {
		return this.shapesAsJson.length > 0;
	},
	refresh: function (selection, shapes, namespace, useNoOffset) {
		this.selection = selection;
		this.SSnamespace = namespace;
		// Store outgoings, targets and parents to restore them later on
		this.outgoings = {};
		this.parents = {};
		this.targets = {};
		this.useOffset = useNoOffset !== true;

		this.shapesAsJson = shapes.map(function (shape) {
			var s = shape.toJSON();
			s.parent = { resourceId: shape.getParentShape().resourceId };
			s.parentIndex = shape.getParentShape().getChildShapes().indexOf(shape)
			return s;
		});
	}
});

ORYX.Plugins.Edit.DeleteCommand = ORYX.Core.Command.extend({
	construct: function (clipboard, facade) {
		this.clipboard = clipboard;
		this.shapesAsJson = clipboard.shapesAsJson;
		this.facade = facade;

		// Store dockers of deleted shapes to restore connections
		this.dockers = this.shapesAsJson.map(function (shapeAsJson) {
			var shape = shapeAsJson.getShape();
			var incomingDockers = shape.getIncomingShapes().map(function (s) { return s.getDockers().last() })
			var outgoingDockers = shape.getOutgoingShapes().map(function (s) { return s.getDockers().first() })
			var dockers = shape.getDockers().concat(incomingDockers, outgoingDockers).compact().map(function (docker) {
				return {
					object: docker,
					referencePoint: docker.referencePoint,
					dockedShape: docker.getDockedShape()
				};
			});
			return dockers;
		}).flatten();
	},
	execute: function () {
		this.shapesAsJson.each(function (shapeAsJson) {
			// Delete shape
			this.facade.deleteShape(shapeAsJson.getShape());
		}.bind(this));

		this.facade.setSelection([]);
		this.facade.getCanvas().update();
		this.facade.updateSelection();

	},
	rollback: function () {
		this.shapesAsJson.each(function (shapeAsJson) {
			var shape = shapeAsJson.getShape();
			var parent = this.facade.getCanvas().getChildShapeByResourceId(shapeAsJson.parent.resourceId) || this.facade.getCanvas();
			parent.add(shape, shape.parentIndex);
		}.bind(this));

		//reconnect shapes
		this.dockers.each(function (d) {
			d.object.setDockedShape(d.dockedShape);
			d.object.setReferencePoint(d.referencePoint);
		}.bind(this));

		this.facade.setSelection(this.selectedShapes);
		this.facade.getCanvas().update();
		this.facade.updateSelection();

	}
});

/**
 * Copyright (c) 2009
 * Jan-Felix Schwarz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/
if (!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.KeysMove = ORYX.Plugins.AbstractPlugin.extend({

	facade: undefined,

	construct: function (facade) {

		this.facade = facade;
		this.copyElements = [];

		//this.facade.registerOnEvent(ORYX.CONFIG.EVENT_KEYDOWN, this.keyHandler.bind(this));

		// SELECT ALL
		this.facade.offer({
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: 65,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.selectAll.bind(this)
		});

		// MOVE LEFT SMALL		
		this.facade.offer({
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: ORYX.CONFIG.KEY_CODE_LEFT,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.move.bind(this, ORYX.CONFIG.KEY_CODE_LEFT, false)
		});

		// MOVE LEFT
		this.facade.offer({
			keyCodes: [{
				keyCode: ORYX.CONFIG.KEY_CODE_LEFT,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.move.bind(this, ORYX.CONFIG.KEY_CODE_LEFT, true)
		});

		// MOVE RIGHT SMALL	
		this.facade.offer({
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: ORYX.CONFIG.KEY_CODE_RIGHT,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.move.bind(this, ORYX.CONFIG.KEY_CODE_RIGHT, false)
		});

		// MOVE RIGHT	
		this.facade.offer({
			keyCodes: [{
				keyCode: ORYX.CONFIG.KEY_CODE_RIGHT,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.move.bind(this, ORYX.CONFIG.KEY_CODE_RIGHT, true)
		});

		// MOVE UP SMALL	
		this.facade.offer({
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: ORYX.CONFIG.KEY_CODE_UP,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.move.bind(this, ORYX.CONFIG.KEY_CODE_UP, false)
		});

		// MOVE UP	
		this.facade.offer({
			keyCodes: [{
				keyCode: ORYX.CONFIG.KEY_CODE_UP,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.move.bind(this, ORYX.CONFIG.KEY_CODE_UP, true)
		});

		// MOVE DOWN SMALL	
		this.facade.offer({
			keyCodes: [{
				metaKeys: [ORYX.CONFIG.META_KEY_META_CTRL],
				keyCode: ORYX.CONFIG.KEY_CODE_DOWN,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.move.bind(this, ORYX.CONFIG.KEY_CODE_DOWN, false)
		});

		// MOVE DOWN	
		this.facade.offer({
			keyCodes: [{
				keyCode: ORYX.CONFIG.KEY_CODE_DOWN,
				keyAction: ORYX.CONFIG.KEY_ACTION_DOWN
			}
			],
			functionality: this.move.bind(this, ORYX.CONFIG.KEY_CODE_DOWN, true)
		});


	},

	/**
	 * Select all shapes in the editor
	 *
	 */
	selectAll: function (e) {
		Event.stop(e.event);
		this.facade.setSelection(this.facade.getCanvas().getChildShapes(true))
	},

	move: function (key, far, e) {

		Event.stop(e.event);

		// calculate the distance to move the objects and get the selection.
		var distance = far ? 20 : 5;
		var selection = this.facade.getSelection();
		var currentSelection = this.facade.getSelection();
		var p = { x: 0, y: 0 };

		// switch on the key pressed and populate the point to move by.
		switch (key) {

			case ORYX.CONFIG.KEY_CODE_LEFT:
				p.x = -1 * distance;
				break;
			case ORYX.CONFIG.KEY_CODE_RIGHT:
				p.x = distance;
				break;
			case ORYX.CONFIG.KEY_CODE_UP:
				p.y = -1 * distance;
				break;
			case ORYX.CONFIG.KEY_CODE_DOWN:
				p.y = distance;
				break;
		}

		// move each shape in the selection by the point calculated and update it.
		selection = selection.findAll(function (shape) {
			// Check if this shape is docked to an shape in the selection			
			if (shape instanceof ORYX.Core.Node && shape.dockers.length == 1 && selection.include(shape.dockers.first().getDockedShape())) {
				return false
			}

			// Check if any of the parent shape is included in the selection
			var s = shape.parent;
			do {
				if (selection.include(s)) {
					return false
				}
			} while (s = s.parent);

			// Otherwise, return true
			return true;

		});

		/* Edges must not be movable, if only edges are selected and at least 
		 * one of them is docked.
		 */
		var edgesMovable = true;
		var onlyEdgesSelected = selection.all(function (shape) {
			if (shape instanceof ORYX.Core.Edge) {
				if (shape.isDocked()) {
					edgesMovable = false;
				}
				return true;
			}
			return false;
		});

		if (onlyEdgesSelected && !edgesMovable) {
			/* Abort moving shapes */
			return;
		}

		selection = selection.map(function (shape) {
			if (shape instanceof ORYX.Core.Node) {
				/*if( shape.dockers.length == 1 ){
					return shape.dockers.first()
				} else {*/
				return shape
				//}
			} else if (shape instanceof ORYX.Core.Edge) {

				var dockers = shape.dockers;

				if (selection.include(shape.dockers.first().getDockedShape())) {
					dockers = dockers.without(shape.dockers.first())
				}

				if (selection.include(shape.dockers.last().getDockedShape())) {
					dockers = dockers.without(shape.dockers.last())
				}

				return dockers

			} else {
				return null
			}

		}).flatten().compact();

		if (selection.size() > 0) {

			//Stop moving at canvas borders
			var selectionBounds = [this.facade.getCanvas().bounds.lowerRight().x,
			this.facade.getCanvas().bounds.lowerRight().y,
				0,
				0];
			selection.each(function (s) {
				selectionBounds[0] = Math.min(selectionBounds[0], s.bounds.upperLeft().x);
				selectionBounds[1] = Math.min(selectionBounds[1], s.bounds.upperLeft().y);
				selectionBounds[2] = Math.max(selectionBounds[2], s.bounds.lowerRight().x);
				selectionBounds[3] = Math.max(selectionBounds[3], s.bounds.lowerRight().y);
			});
			if (selectionBounds[0] + p.x < 0)
				p.x = -selectionBounds[0];
			if (selectionBounds[1] + p.y < 0)
				p.y = -selectionBounds[1];
			if (selectionBounds[2] + p.x > this.facade.getCanvas().bounds.lowerRight().x)
				p.x = this.facade.getCanvas().bounds.lowerRight().x - selectionBounds[2];
			if (selectionBounds[3] + p.y > this.facade.getCanvas().bounds.lowerRight().y)
				p.y = this.facade.getCanvas().bounds.lowerRight().y - selectionBounds[3];

			if (p.x != 0 || p.y != 0) {
				// Instantiate the moveCommand
				var commands = [new ORYX.Core.Command.Move(selection, p, null, currentSelection, this)];
				// Execute the commands			
				this.facade.executeCommands(commands);
			}

		}
	},

	getUndockedCommant: function (shapes) {

		var undockEdgeCommand = ORYX.Core.Command.extend({
			construct: function (moveShapes) {
				this.dockers = moveShapes.collect(function (shape) { return shape instanceof ORYX.Core.Controls.Docker ? { docker: shape, dockedShape: shape.getDockedShape(), refPoint: shape.referencePoint } : undefined }).compact();
			},
			execute: function () {
				this.dockers.each(function (el) {
					el.docker.setDockedShape(undefined);
				})
			},
			rollback: function () {
				this.dockers.each(function (el) {
					el.docker.setDockedShape(el.dockedShape);
					el.docker.setReferencePoint(el.refPoint);
					//el.docker.update();
				})
			}
		});

		command = new undockEdgeCommand(shapes);
		command.execute();
		return command;
	},



});
/**
 * Copyright (c) 2009
 * Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins) { ORYX.Plugins = {} }
if (!ORYX.Plugins.Layouter) { ORYX.Plugins.Layouter = {} }


/**
 * Edge layouter is an implementation to layout an edge
 * @class ORYX.Plugins.Layouter.EdgeLayouter
 * @author Willi Tscheschner
 */
ORYX.Plugins.Layouter.EdgeLayouter = ORYX.Plugins.AbstractLayouter.extend({

	/**
	 * Layout only Edges
	 */
	layouted: ["http://b3mn.org/stencilset/bpmn1.1#SequenceFlow",
		"http://b3mn.org/stencilset/bpmn1.1#MessageFlow",
		"http://b3mn.org/stencilset/timjpdl3#SequenceFlow",
		"http://b3mn.org/stencilset/jbpm4#SequenceFlow",
		"http://b3mn.org/stencilset/bpmn2.0#MessageFlow",
		"http://b3mn.org/stencilset/bpmn2.0#SequenceFlow",
		"http://b3mn.org/stencilset/bpmn2.0choreography#MessageFlow",
		"http://b3mn.org/stencilset/bpmn2.0choreography#SequenceFlow",
		"http://b3mn.org/stencilset/bpmn2.0conversation#ConversationLink",
		"http://b3mn.org/stencilset/epc#ControlFlow",
		"http://www.signavio.com/stencilsets/processmap#ProcessLink",
		"http://www.signavio.com/stencilsets/organigram#connection"],

	/**
	 * Layout a set on edges
	 * @param {Object} edges
	 */
	layout: function (edges) {
		edges.each(function (edge) {
			this.doLayout(edge)
		}.bind(this))
	},

	/**
	 * Layout one edge
	 * @param {Object} edge
	 */
	doLayout: function (edge) {
		// Get from and to node
		var from = edge.getIncomingNodes()[0];
		var to = edge.getOutgoingNodes()[0];

		// Return if one is null
		if (!from || !to) { return }

		var positions = this.getPositions(from, to, edge);

		if (positions.length > 0) {
			this.setDockers(edge, positions[0].a, positions[0].b);
		}

	},

	/**
	 * Returns a set on positions which are not containt either 
	 * in the bounds in from or to.
	 * @param {Object} from Shape where the edge is come from
	 * @param {Object} to Shape where the edge is leading to
	 * @param {Object} edge Edge between from and to
	 */
	getPositions: function (from, to, edge) {

		// Get absolute bounds
		var ab = from.absoluteBounds();
		var bb = to.absoluteBounds();

		// Get center from and to
		var a = ab.center();
		var b = bb.center();

		var am = ab.midPoint();
		var bm = bb.midPoint();

		// Get first and last reference point
		var first = Object.clone(edge.dockers.first().referencePoint);
		var last = Object.clone(edge.dockers.last().referencePoint);
		// Get the absolute one
		var aFirst = edge.dockers.first().getAbsoluteReferencePoint();
		var aLast = edge.dockers.last().getAbsoluteReferencePoint();

		// IF ------>
		// or  |
		//     V
		// Do nothing
		if (Math.abs(aFirst.x - aLast.x) < 1 || Math.abs(aFirst.y - aLast.y) < 1) {
			return []
		}

		// Calc center position, between a and b
		// depending on there weight
		var m = {}
		m.x = a.x < b.x ?
			(((b.x - bb.width() / 2) - (a.x + ab.width() / 2)) / 2) + (a.x + ab.width() / 2) :
			(((a.x - ab.width() / 2) - (b.x + bb.width() / 2)) / 2) + (b.x + bb.width() / 2);

		m.y = a.y < b.y ?
			(((b.y - bb.height() / 2) - (a.y + ab.height() / 2)) / 2) + (a.y + ab.height() / 2) :
			(((a.y - ab.height() / 2) - (b.y + bb.height() / 2)) / 2) + (b.y + bb.height() / 2);


		// Enlarge both bounds with 10
		ab.widen(5); // Wide the from less than 
		bb.widen(20);// the to because of the arrow from the edge

		var positions = [];
		var off = this.getOffset.bind(this);

		// Checks ----+
		//            |
		//            V
		if (!ab.isIncluded(b.x, a.y) && !bb.isIncluded(b.x, a.y)) {
			positions.push({
				a: { x: b.x + off(last, bm, "x"), y: a.y + off(first, am, "y") },
				z: this.getWeight(from, a.x < b.x ? "r" : "l", to, a.y < b.y ? "t" : "b", edge)
			});
		}

		// Checks | 
		//        +--->
		if (!ab.isIncluded(a.x, b.y) && !bb.isIncluded(a.x, b.y)) {
			positions.push({
				a: { x: a.x + off(first, am, "x"), y: b.y + off(last, bm, "y") },
				z: this.getWeight(from, a.y < b.y ? "b" : "t", to, a.x < b.x ? "l" : "r", edge)
			});
		}

		// Checks  --+
		//           |
		//           +--->
		if (!ab.isIncluded(m.x, a.y) && !bb.isIncluded(m.x, b.y)) {
			positions.push({
				a: { x: m.x, y: a.y + off(first, am, "y") },
				b: { x: m.x, y: b.y + off(last, bm, "y") },
				z: this.getWeight(from, "r", to, "l", edge, a.x > b.x)
			});
		}

		// Checks | 
		//        +---+
		//            |
		//            V
		if (!ab.isIncluded(a.x, m.y) && !bb.isIncluded(b.x, m.y)) {
			positions.push({
				a: { x: a.x + off(first, am, "x"), y: m.y },
				b: { x: b.x + off(last, bm, "x"), y: m.y },
				z: this.getWeight(from, "b", to, "t", edge, a.y > b.y)
			});
		}

		// Sort DESC of weights
		return positions.sort(function (a, b) { return a.z < b.z ? 1 : (a.z == b.z ? -1 : -1) });
	},

	/**
	 * Returns a offset for the pos to the center of the bounds
	 * 
	 * @param {Object} val
	 * @param {Object} pos2
	 * @param {String} dir Direction x|y
	 */
	getOffset: function (pos, pos2, dir) {
		return pos[dir] - pos2[dir];
	},

	/**
	 * Returns a value which shows the weight for this configuration
	 * 
	 * @param {Object} from Shape which is coming from
	 * @param {String} d1 Direction where is goes
	 * @param {Object} to Shape which goes to
	 * @param {String} d2 Direction where it comes to
	 * @param {Object} edge Edge between from and to
	 * @param {Boolean} reverse Reverse the direction (e.g. "r" -> "l")
	 */
	getWeight: function (from, d1, to, d2, edge, reverse) {

		d1 = (d1 || "").toLowerCase();
		d2 = (d2 || "").toLowerCase();

		if (!["t", "r", "b", "l"].include(d1)) { d1 = "r" }
		if (!["t", "r", "b", "l"].include(d2)) { d1 = "l" }

		// If reverse is set
		if (reverse) {
			// Reverse d1 and d2
			d1 = d1 == "t" ? "b" : (d1 == "r" ? "l" : (d1 == "b" ? "t" : (d1 == "l" ? "r" : "r")))
			d2 = d2 == "t" ? "b" : (d2 == "r" ? "l" : (d2 == "b" ? "t" : (d2 == "l" ? "r" : "r")))
		}


		var weight = 0;
		// Get rules for from "out" and to "in"
		var dr1 = this.facade.getRules().getLayoutingRules(from, edge)["out"];
		var dr2 = this.facade.getRules().getLayoutingRules(to, edge)["in"];

		var fromWeight = dr1[d1];
		var toWeight = dr2[d2];


		/**
		 * Return a true if the center 1 is in the same direction than center 2
		 * @param {Object} direction
		 * @param {Object} center1
		 * @param {Object} center2
		 */
		var sameDirection = function (direction, center1, center2) {
			switch (direction) {
				case "t": return Math.abs(center1.x - center2.x) < 2 && center1.y < center2.y
				case "r": return center1.x > center2.x && Math.abs(center1.y - center2.y) < 2
				case "b": return Math.abs(center1.x - center2.x) < 2 && center1.y > center2.y
				case "l": return center1.x < center2.x && Math.abs(center1.y - center2.y) < 2
				default: return false;
			}
		}

		// Check if there are same incoming edges from 'from'
		var sameIncomingFrom = from
			.getIncomingShapes()
			.findAll(function (a) { return a instanceof ORYX.Core.Edge })
			.any(function (e) {
				return sameDirection(d1, e.dockers[e.dockers.length - 2].bounds.center(), e.dockers.last().bounds.center());
			});

		// Check if there are same outgoing edges from 'to'
		var sameOutgoingTo = to
			.getOutgoingShapes()
			.findAll(function (a) { return a instanceof ORYX.Core.Edge })
			.any(function (e) {
				return sameDirection(d2, e.dockers[1].bounds.center(), e.dockers.first().bounds.center());
			});

		// If there are equivalent edges, set 0
		//fromWeight = sameIncomingFrom ? 0 : fromWeight;
		//toWeight = sameOutgoingTo ? 0 : toWeight;

		// Get the sum of "out" and the direction plus "in" and the direction 						
		return (sameIncomingFrom || sameOutgoingTo ? 0 : fromWeight + toWeight);
	},

	/**
	 * Removes all current dockers from the node 
	 * (except the start and end) and adds two new
	 * dockers, on the position a and b.
	 * @param {Object} edge
	 * @param {Object} a
	 * @param {Object} b
	 */
	setDockers: function (edge, a, b) {
		if (!edge) { return }

		// Remove all dockers (implicit,
		// start and end dockers will not removed)
		edge.dockers.each(function (r) {
			edge.removeDocker(r);
		});

		// For a and b (if exists), create
		// a new docker and set position
		[a, b].compact().each(function (pos) {
			var docker = edge.createDocker(undefined, pos);
			docker.bounds.centerMoveTo(pos);
		});

		// Update all dockers from the edge
		edge.dockers.each(function (docker) {
			docker.update()
		})

		// Update edge
		//edge.refresh();
		edge._update(true);

	}
});

/**
 * Copyright (c) 2009
 * Sven Wagner-Boysen, Willi Tscheschner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if (!ORYX.Plugins)
	ORYX.Plugins = new Object();


ORYX.Plugins.BPMN2_0 = {

	/**
	 *	Constructor
	 *	@param {Object} Facade: The Facade of the Editor
	 */
	construct: function (facade) {
		this.facade = facade;

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_DRAGDOCKER_DOCKED, this.handleDockerDocked.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED, this.handlePropertyChanged.bind(this));
		this.facade.registerOnEvent('layout.bpmn2_0.pool', this.handleLayoutPool.bind(this));
		this.facade.registerOnEvent('layout.bpmn2_0.subprocess', this.handleSubProcess.bind(this));
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_SHAPEREMOVED, this.handleShapeRemove.bind(this));
		//this.facade.registerOnEvent('layout.bpmn11.lane', this.handleLayoutLane.bind(this));

		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_LOADED, this.afterLoad.bind(this));


		this.namespace = undefined;
	},

	/**
	 * Force to update every pool
	 */
	afterLoad: function () {
		this.facade.getCanvas().getChildNodes().each(function (shape) {
			if (shape.getStencil().id().endsWith("Pool")) {
				this.handleLayoutPool({
					shape: shape
				});
			}
		}.bind(this))
	},

	/**
	 * If a pool is selected and contains no lane,
	 * a lane is created automagically
	 */
	onSelectionChanged: function (event) {
		var selection = event.elements;

		if (selection && selection.length === 1) {
			var namespace = this.getNamespace();
			var shape = selection[0];
			if (shape.getStencil().idWithoutNs() === "Pool") {
				if (shape.getChildNodes().length === 0) {
					// create a lane inside the selected pool
					var option = {
						type: namespace + "Lane",
						position: { x: 0, y: 0 },
						namespace: shape.getStencil().namespace(),
						parent: shape
					};
					this.facade.createShape(option);
					this.facade.getCanvas().update();
					this.facade.setSelection([shape]);
				}
			}
		}

		// Preventing selection of all lanes but not the pool
		if (selection.any(function (s) { return s instanceof ORYX.Core.Node && s.getStencil().id().endsWith("Lane") })) {
			var lanes = selection.findAll(function (s) {
				return s instanceof ORYX.Core.Node && s.getStencil().id().endsWith("Lane")
			});

			var pools = [];
			var unselectLanes = [];
			lanes.each(function (lane) {
				pools.push(this.getParentPool(lane))
			}.bind(this));

			pools = pools.uniq().findAll(function (pool) {
				var childLanes = this.getLanes(pool, true);
				if (childLanes.all(function (lane) { return lanes.include(lane) })) {
					unselectLanes = unselectLanes.concat(childLanes);
					return true;
				} else if (selection.include(pool) && childLanes.any(function (lane) { return lanes.include(lane) })) {
					unselectLanes = unselectLanes.concat(childLanes);
					return true;
				} else {
					return false;
				}
			}.bind(this))

			if (unselectLanes.length > 0 && pools.length > 0) {
				selection = selection.without.apply(selection, unselectLanes);
				selection = selection.concat(pools);
				this.facade.setSelection(selection.uniq());
			}
		}
	},

	handleShapeRemove: function (option) {

		var sh = option.shape;
		var parent = option.parent;

		if (sh instanceof ORYX.Core.Node && sh.getStencil().idWithoutNs() === "Lane" && this.facade.isExecutingCommands()) {

			var pool = this.getParentPool(parent);
			if (pool && pool.parent) {

				var isLeafFn = function (leaf) {
					return !leaf.getChildNodes().any(function (r) { return r.getStencil().idWithoutNs() === "Lane" });
				}

				var isLeaf = isLeafFn(sh);
				var parentHasMoreLanes = parent.getChildNodes().any(function (r) { return r.getStencil().idWithoutNs() === "Lane" });

				if (isLeaf && parentHasMoreLanes) {

					var command = new ResizeLanesCommand(sh, parent, pool, this);
					this.facade.executeCommands([command]);

				} else if (!isLeaf &&
					!this.facade.getSelection().any(function (select) { // Find one of the selection, which is a lane and child of "sh" and is a leaf lane
						return select instanceof ORYX.Core.Node && select.getStencil().idWithoutNs() === "Lane" &&
							select.isParent(sh) && isLeafFn(select);
					})) {

					var Command = ORYX.Core.Command.extend({
						construct: function (shape, facade) {
							this.children = shape.getChildNodes(true);
							this.facade = facade;
						},
						execute: function () {
							this.children.each(function (child) {
								child.bounds.moveBy(30, 0)
							});
							//this.facade.getCanvas().update();
						},
						rollback: function () {
							this.children.each(function (child) {
								child.bounds.moveBy(-30, 0)
							})
							//this.facade.getCanvas().update();
						}
					});
					this.facade.executeCommands([new Command(sh, this.facade)]);

				} else if (isLeaf && !parentHasMoreLanes && parent == pool) {
					parent.add(sh);
				}
			}

		}

	},

	hashedSubProcesses: {},

	hashChildShapes: function (shape) {
		var children = shape.getChildNodes();
		children.each(function (child) {
			if (this.hashedSubProcesses[child.id]) {
				this.hashedSubProcesses[child.id] = child.absoluteXY();
				this.hashedSubProcesses[child.id].width = child.bounds.width();
				this.hashedSubProcesses[child.id].height = child.bounds.height();
				this.hashChildShapes(child);
			}
		}.bind(this));
	},

	/**
	 * Handle the layouting of a sub process.
	 * Mainly to adjust the child dockers of a sub process. 
	 *
	 */
	handleSubProcess: function (option) {

		var sh = option.shape;

		if (!this.hashedSubProcesses[sh.id]) {
			this.hashedSubProcesses[sh.id] = sh.absoluteXY();
			this.hashedSubProcesses[sh.id].width = sh.bounds.width();
			this.hashedSubProcesses[sh.id].height = sh.bounds.height();
			return;
		}

		var offset = sh.absoluteXY();
		offset.x -= this.hashedSubProcesses[sh.id].x;
		offset.y -= this.hashedSubProcesses[sh.id].y;

		var resized = this.hashedSubProcesses[sh.id].width !== sh.bounds.width() || this.hashedSubProcesses[sh.id].height !== sh.bounds.height();

		this.hashedSubProcesses[sh.id] = sh.absoluteXY();
		this.hashedSubProcesses[sh.id].width = sh.bounds.width();
		this.hashedSubProcesses[sh.id].height = sh.bounds.height();
		this.hashChildShapes(sh);


		// Move dockers only if currently is not resizing
		if (this.facade.isExecutingCommands() && !resized) {
			this.moveChildDockers(sh, offset);
		}
	},

	moveChildDockers: function (shape, offset) {

		if (!offset.x && !offset.y) {
			return;
		}

		var children = shape.getChildNodes(true);

		// Get all nodes
		var dockers = children
			// Get all incoming and outgoing edges
			.map(function (node) {
				return [].concat(node.getIncomingShapes())
					.concat(node.getOutgoingShapes())
			})
			// Flatten all including arrays into one
			.flatten()
			// Get every edge only once
			.uniq()
			// Get all dockers
			.map(function (edge) {
				return edge.dockers.length > 2 ?
					edge.dockers.slice(1, edge.dockers.length - 1) :
					[];
			})
			// Flatten the dockers lists
			.flatten();

		var abs = shape.absoluteBounds();
		abs.moveBy(-offset.x, -offset.y)
		var obj = {};
		dockers.each(function (docker) {

			if (docker.isChanged) {
				return;
			}

			var off = Object.clone(offset);

			if (!abs.isIncluded(docker.bounds.center())) {
				var index = docker.parent.dockers.indexOf(docker);
				var size = docker.parent.dockers.length;
				var from = docker.parent.getSource();
				var to = docker.parent.getTarget();

				var bothAreIncluded = children.include(from) && children.include(to);

				if (!bothAreIncluded) {
					var previousIsOver = index !== 0 ? abs.isIncluded(docker.parent.dockers[index - 1].bounds.center()) : false;
					var nextIsOver = index !== size - 1 ? abs.isIncluded(docker.parent.dockers[index + 1].bounds.center()) : false;

					if (!previousIsOver && !nextIsOver) { return; }

					var ref = docker.parent.dockers[previousIsOver ? index - 1 : index + 1];
					if (Math.abs(-Math.abs(ref.bounds.center().x - docker.bounds.center().x)) < 2) {
						off.y = 0;
					} else if (Math.abs(-Math.abs(ref.bounds.center().y - docker.bounds.center().y)) < 2) {
						off.x = 0;
					} else {
						return;
					}
				}

			}

			obj[docker.getId()] = {
				docker: docker,
				offset: off
			}
		})

		// Set dockers
		this.facade.executeCommands([new ORYX.Core.MoveDockersCommand(obj)]);

	},

	/**
	 * DragDocker.Docked Handler
	 *
	 */
	handleDockerDocked: function (options) {
		var namespace = this.getNamespace();

		var edge = options.parent;
		var edgeSource = options.target;

		if (edge.getStencil().id() === namespace + "SequenceFlow") {
			var isGateway = edgeSource.getStencil().groups().find(function (group) {
				if (group == "Gateways")
					return group;
			});
			if (!isGateway && (edge.properties["oryx-conditiontype"] == "Expression"))
				// show diamond on edge source
				edge.setProperty("oryx-showdiamondmarker", true);
			else
				// do not show diamond on edge source
				edge.setProperty("oryx-showdiamondmarker", false);

			// update edge rendering
			//edge.update();

			this.facade.getCanvas().update();
		}
	},

	/**
	 * PropertyWindow.PropertyChanged Handler
	 */
	handlePropertyChanged: function (option) {
		var namespace = this.getNamespace();

		var shapes = option.elements;
		var propertyKey = option.key;
		var propertyValue = option.value;

		var changed = false;
		shapes.each(function (shape) {
			if ((shape.getStencil().id() === namespace + "SequenceFlow") &&
				(propertyKey === "oryx-conditiontype")) {

				if (propertyValue != "Expression")
					// Do not show the Diamond
					shape.setProperty("oryx-showdiamondmarker", false);
				else {
					var incomingShapes = shape.getIncomingShapes();

					if (!incomingShapes) {
						shape.setProperty("oryx-showdiamondmarker", true);
					}

					var incomingGateway = incomingShapes.find(function (aShape) {
						var foundGateway = aShape.getStencil().groups().find(function (group) {
							if (group == "Gateways")
								return group;
						});
						if (foundGateway)
							return foundGateway;
					});

					if (!incomingGateway)
						// show diamond on edge source
						shape.setProperty("oryx-showdiamondmarker", true);
					else
						// do not show diamond
						shape.setProperty("oryx-showdiamondmarker", false);
				}

				changed = true;
			}
		}.bind(this));

		if (changed) { this.facade.getCanvas().update(); }

	},

	hashedPoolPositions: {},
	hashedLaneDepth: {},
	hashedBounds: {},
	hashedPositions: {},

	/**
	 * Handler for layouting event 'layout.bpmn2_0.pool'
	 * @param {Object} event
	 */
	handleLayoutPool: function (event) {


		var pool = event.shape;
		var selection = this.facade.getSelection();
		var currentShape = selection.include(pool) ? pool : selection.first();

		currentShape = currentShape || pool;

		this.currentPool = pool;

		// Check if it is a pool or a lane
		if (!(currentShape.getStencil().id().endsWith("Pool") || currentShape.getStencil().id().endsWith("Lane"))) {
			return;
		}

		// Check if the lane is within the pool and is not removed lately 
		if (currentShape !== pool && !currentShape.isParent(pool) && !this.hashedBounds[pool.id][currentShape.id]) {
			return;
		}


		if (!this.hashedBounds[pool.id]) {
			this.hashedBounds[pool.id] = {};
		}

		// Find all child lanes
		var lanes = this.getLanes(pool);

		if (lanes.length <= 0) {
			return
		}

		var allLanes = this.getLanes(pool, true), hp;
		var considerForDockers = allLanes.clone();

		var hashedPositions = $H({});
		allLanes.each(function (lane) {
			hashedPositions[lane.id] = lane.bounds.upperLeft();
		})



		// Show/hide caption regarding the number of lanes
		if (lanes.length === 1 && this.getLanes(lanes.first()).length <= 0) {
			// TRUE if there is a caption
			lanes.first().setProperty("oryx-showcaption", lanes.first().properties["oryx-name"].trim().length > 0);
			var rect = lanes.first().node.getElementsByTagName("rect");
			rect[0].setAttributeNS(null, "display", "none");
		} else {
			allLanes.invoke("setProperty", "oryx-showcaption", true);
			allLanes.each(function (lane) {
				var rect = lane.node.getElementsByTagName("rect");
				rect[0].removeAttributeNS(null, "display");
			})
		}

		var deletedLanes = [];
		var addedLanes = [];

		// Get all new lanes
		var i = -1;
		while (++i < allLanes.length) {
			if (!this.hashedBounds[pool.id][allLanes[i].id]) {
				addedLanes.push(allLanes[i])
			}
		}

		if (addedLanes.length > 0) {
			currentShape = addedLanes.first();
		}


		// Get all deleted lanes
		var resourceIds = $H(this.hashedBounds[pool.id]).keys();
		var i = -1;
		while (++i < resourceIds.length) {
			if (!allLanes.any(function (lane) { return lane.id == resourceIds[i] })) {
				deletedLanes.push(this.hashedBounds[pool.id][resourceIds[i]]);
				selection = selection.without(function (r) { return r.id == resourceIds[i] });
			}
		}

		var height, width, x, y;

		if (deletedLanes.length > 0 || addedLanes.length > 0) {

			if (addedLanes.length === 1 && this.getLanes(addedLanes[0].parent).length === 1) {
				// Set height from the pool
				height = this.adjustHeight(lanes, addedLanes[0].parent);
			} else {
				// Set height from the pool
				height = this.updateHeight(pool);
			}
			// Set width from the pool
			width = this.adjustWidth(lanes, pool.bounds.width());

			pool.update();
		}

		/**
		 * Set width/height depending on the pool
		 */
		else if (pool == currentShape) {

			if (selection.length === 1 && this.isResized(pool, this.hashedPoolPositions[pool.id])) {
				var oldXY = this.hashedPoolPositions[pool.id].upperLeft();
				var xy = pool.bounds.upperLeft();
				var scale = 0;
				if (this.shouldScale(pool)) {
					var old = this.hashedPoolPositions[pool.id];
					scale = old.height() / pool.bounds.height();
				}

				this.adjustLanes(pool, allLanes, oldXY.x - xy.x, oldXY.y - xy.y, scale);
			}

			// Set height from the pool
			height = this.adjustHeight(lanes, undefined, pool.bounds.height());
			// Set width from the pool
			width = this.adjustWidth(lanes, pool.bounds.width());
		}

		/**‚
		 * Set width/height depending on containing lanes
		 */
		else {

			// Reposition the pool if one shape is selected and the upperleft has changed
			if (selection.length === 1 && this.isResized(currentShape, this.hashedBounds[pool.id][currentShape.id])) {
				var oldXY = this.hashedBounds[pool.id][currentShape.id].upperLeft();
				var xy = currentShape.absoluteXY();
				x = oldXY.x - xy.x;
				y = oldXY.y - xy.y;

				// Adjust all other lanes beneath this lane
				if (x || y) {
					considerForDockers = considerForDockers.without(currentShape);
					this.adjustLanes(pool, this.getAllExcludedLanes(pool, currentShape), x, 0);
				}

				// Adjust all child lanes
				var childLanes = this.getLanes(currentShape, true);
				if (childLanes.length > 0) {
					if (this.shouldScale(currentShape)) {
						var old = this.hashedBounds[pool.id][currentShape.id];
						var scale = old.height() / currentShape.bounds.height();
						this.adjustLanes(pool, childLanes, x, y, scale);
					} else {
						this.adjustLanes(pool, childLanes, x, y, 0);
					}
				}
			}

			// Cache all bounds
			var changes = allLanes.map(function (lane) {
				return {
					shape: lane,
					bounds: lane.bounds.clone()
				}
			});

			// Get height and adjust child heights
			height = this.adjustHeight(lanes, currentShape);
			// Check if something has changed and maybe create a command
			this.checkForChanges(allLanes, changes);

			// Set width from the current shape
			width = this.adjustWidth(lanes, currentShape.bounds.width() + (this.getDepth(currentShape, pool) * 30));
		}

		this.setDimensions(pool, width, height, x, y);


		if (this.facade.isExecutingCommands() && (deletedLanes.length === 0 || addedLanes.length !== 0)) {
			// Update all dockers
			this.updateDockers(considerForDockers, pool);

			// Check if the order has changed
			if (this.hashedPositions[pool.id] && this.hashedPositions[pool.id].keys().any(function (key, i) {
				return (allLanes[i] || {}).id !== key;
			})) {

				var LanesHasBeenReordered = ORYX.Core.Command.extend({
					construct: function (originPosition, newPosition, lanes, plugin, poolId) {
						this.originPosition = Object.clone(originPosition);
						this.newPosition = Object.clone(newPosition);
						this.lanes = lanes;
						this.plugin = plugin;
						this.pool = poolId;
					},
					execute: function () {
						if (!this.executed) {
							this.executed = true;
							this.lanes.each(function (lane) {
								if (this.newPosition[lane.id])
									lane.bounds.moveTo(this.newPosition[lane.id])
							}.bind(this));
							this.plugin.hashedPositions[this.pool] = Object.clone(this.newPosition);
						}
					},
					rollback: function () {
						this.lanes.each(function (lane) {
							if (this.originPosition[lane.id])
								lane.bounds.moveTo(this.originPosition[lane.id])
						}.bind(this));
						this.plugin.hashedPositions[this.pool] = Object.clone(this.originPosition);
					}
				});

				var hp2 = $H({});
				allLanes.each(function (lane) {
					hp2[lane.id] = lane.bounds.upperLeft();
				})

				var command = new LanesHasBeenReordered(hashedPositions, hp2, allLanes, this, pool.id);
				this.facade.executeCommands([command]);

			}
		}

		this.hashedBounds[pool.id] = {};
		this.hashedPositions[pool.id] = hashedPositions;

		var i = -1;
		while (++i < allLanes.length) {
			// Cache positions
			this.hashedBounds[pool.id][allLanes[i].id] = allLanes[i].absoluteBounds();

			// Cache also the bounds of child shapes, mainly for child subprocesses
			this.hashChildShapes(allLanes[i]);

			this.hashedLaneDepth[allLanes[i].id] = this.getDepth(allLanes[i], pool);

			this.forceToUpdateLane(allLanes[i]);
		}

		this.hashedPoolPositions[pool.id] = pool.bounds.clone();


		// Update selection
		//this.facade.setSelection(selection);		
	},

	shouldScale: function (element) {
		var childLanes = element.getChildNodes().findAll(function (shape) { return shape.getStencil().id().endsWith("Lane") })
		return childLanes.length > 1 || childLanes.any(function (lane) { return this.shouldScale(lane) }.bind(this))
	},

	/**
	 * Lookup if some bounds has changed
	 * @param {Object} lanes
	 * @param {Object} changes
	 */
	checkForChanges: function (lanes, changes) {
		// Check if something has changed
		if (this.facade.isExecutingCommands() && changes.any(function (change) {
			return change.shape.bounds.toString() !== change.bounds.toString();
		})) {

			var Command = ORYX.Core.Command.extend({
				construct: function (changes) {
					this.oldState = changes;
					this.newState = changes.map(function (s) { return { shape: s.shape, bounds: s.bounds.clone() } });
				},
				execute: function () {
					if (this.executed) {
						this.applyState(this.newState);
					}
					this.executed = true;
				},
				rollback: function () {
					this.applyState(this.oldState);
				},
				applyState: function (state) {
					state.each(function (s) {
						s.shape.bounds.set(s.bounds.upperLeft(), s.bounds.lowerRight());
					})
				}
			});

			this.facade.executeCommands([new Command(changes)]);
		}
	},

	isResized: function (shape, bounds) {

		if (!bounds || !shape) {
			return false;
		}

		var oldB = bounds;
		//var oldXY = oldB.upperLeft();
		//var xy = shape.absoluteXY();
		return Math.round(oldB.width() - shape.bounds.width()) !== 0 || Math.round(oldB.height() - shape.bounds.height()) !== 0
	},

	adjustLanes: function (pool, lanes, x, y, scale) {

		scale = scale || 0;

		// For every lane, adjust the child nodes with the offset
		lanes.each(function (l) {
			l.getChildNodes().each(function (child) {
				if (!child.getStencil().id().endsWith("Lane")) {
					var cy = scale ? child.bounds.center().y - (child.bounds.center().y / scale) : -y;
					child.bounds.moveBy((x || 0), -cy);

					if (scale && child.getStencil().id().endsWith("Subprocess")) {
						this.moveChildDockers(child, { x: (0), y: -cy });
					}

				}
			}.bind(this));
			this.hashedBounds[pool.id][l.id].moveBy(-(x || 0), !scale ? -y : 0);
			if (scale) {
				l.isScaled = true;
			}
		}.bind(this))

	},

	getAllExcludedLanes: function (parent, lane) {
		var lanes = [];
		parent.getChildNodes().each(function (shape) {
			if ((!lane || shape !== lane) && shape.getStencil().id().endsWith("Lane")) {
				lanes.push(shape);
				lanes = lanes.concat(this.getAllExcludedLanes(shape, lane));
			}
		}.bind(this));
		return lanes;
	},


	forceToUpdateLane: function (lane) {

		if (lane.bounds.height() !== lane._svgShapes[0].height) {
			lane.isChanged = true;
			lane.isResized = true;
			lane._update();
		}
	},

	getDepth: function (child, parent) {

		var i = 0;
		while (child && child.parent && child !== parent) {
			child = child.parent;
			++i
		}
		return i;
	},

	updateDepth: function (lane, fromDepth, toDepth) {

		var xOffset = (fromDepth - toDepth) * 30;

		lane.getChildNodes().each(function (shape) {
			shape.bounds.moveBy(xOffset, 0);

			[].concat(children[j].getIncomingShapes())
				.concat(children[j].getOutgoingShapes())

		})

	},

	setDimensions: function (shape, width, height, x, y) {
		var isLane = shape.getStencil().id().endsWith("Lane");
		// Set the bounds
		shape.bounds.set(
			isLane ? 30 : (shape.bounds.a.x - (x || 0)),
			isLane ? shape.bounds.a.y : (shape.bounds.a.y - (y || 0)),
			width ? shape.bounds.a.x + width - (isLane ? 30 : (x || 0)) : shape.bounds.b.x,
			height ? shape.bounds.a.y + height - (isLane ? 0 : (y || 0)) : shape.bounds.b.y
		);
	},

	setLanePosition: function (shape, y) {

		shape.bounds.moveTo(30, y);

	},

	adjustWidth: function (lanes, width) {

		// Set width to each lane
		(lanes || []).each(function (lane) {
			this.setDimensions(lane, width);
			this.adjustWidth(this.getLanes(lane), width - 30);
		}.bind(this));

		return width;
	},


	adjustHeight: function (lanes, changedLane, propagateHeight) {

		var oldHeight = 0;
		if (!changedLane && propagateHeight) {
			var i = -1;
			while (++i < lanes.length) {
				oldHeight += lanes[i].bounds.height();
			}
		}

		var i = -1;
		var height = 0;

		// Iterate trough every lane
		while (++i < lanes.length) {

			if (lanes[i] === changedLane) {
				// Propagate new height down to the children
				this.adjustHeight(this.getLanes(lanes[i]), undefined, lanes[i].bounds.height());

				lanes[i].bounds.set({ x: 30, y: height }, { x: lanes[i].bounds.width() + 30, y: lanes[i].bounds.height() + height })

			} else if (!changedLane && propagateHeight) {

				var tempHeight = (lanes[i].bounds.height() * propagateHeight) / oldHeight;
				// Propagate height
				this.adjustHeight(this.getLanes(lanes[i]), undefined, tempHeight);
				// Set height propotional to the propagated and old height
				this.setDimensions(lanes[i], null, tempHeight);
				this.setLanePosition(lanes[i], height);
			} else {
				// Get height from children
				var tempHeight = this.adjustHeight(this.getLanes(lanes[i]), changedLane, propagateHeight);
				if (!tempHeight) {
					tempHeight = lanes[i].bounds.height();
				}
				this.setDimensions(lanes[i], null, tempHeight);
				this.setLanePosition(lanes[i], height);
			}

			height += lanes[i].bounds.height();
		}

		return height;

	},


	updateHeight: function (root) {

		var lanes = this.getLanes(root);

		if (lanes.length == 0) {
			return root.bounds.height();
		}

		var height = 0;
		var i = -1;
		while (++i < lanes.length) {
			this.setLanePosition(lanes[i], height);
			height += this.updateHeight(lanes[i]);
		}

		this.setDimensions(root, null, height);

		return height;
	},

	getOffset: function (lane, includePool, pool) {

		var offset = { x: 0, y: 0 };


		/*var parent = lane; 
		 while(parent) {
							
			
			var offParent = this.hashedBounds[pool.id][parent.id] ||(includePool === true ? this.hashedPoolPositions[parent.id] : undefined);
			if (offParent){
				var ul = parent.bounds.upperLeft();
				var ulo = offParent.upperLeft();
				offset.x += ul.x-ulo.x;
				offset.y += ul.y-ulo.y;
			}
			
			if (parent.getStencil().id().endsWith("Pool")) {
				break;
			}
			
			parent = parent.parent;
		}	*/

		var offset = lane.absoluteXY();

		var hashed = this.hashedBounds[pool.id][lane.id] || (includePool === true ? this.hashedPoolPositions[lane.id] : undefined);
		if (hashed) {
			offset.x -= hashed.upperLeft().x;
			offset.y -= hashed.upperLeft().y;
		} else {
			return { x: 0, y: 0 }
		}
		return offset;
	},

	getNextLane: function (shape) {
		while (shape && !shape.getStencil().id().endsWith("Lane")) {
			if (shape instanceof ORYX.Core.Canvas) {
				return null;
			}
			shape = shape.parent;
		}
		return shape;
	},

	getParentPool: function (shape) {
		while (shape && !shape.getStencil().id().endsWith("Pool")) {
			if (shape instanceof ORYX.Core.Canvas) {
				return null;
			}
			shape = shape.parent;
		}
		return shape;
	},

	updateDockers: function (lanes, pool) {

		var absPool = pool.absoluteBounds(), movedShapes = [];
		var oldPool = (this.hashedPoolPositions[pool.id] || absPool).clone();

		var i = -1, j = -1, k = -1, l = -1, docker;
		var dockers = {};

		while (++i < lanes.length) {

			if (!this.hashedBounds[pool.id][lanes[i].id]) {
				continue;
			}

			var isScaled = lanes[i].isScaled;
			delete lanes[i].isScaled;
			var children = lanes[i].getChildNodes();
			var absBounds = lanes[i].absoluteBounds();
			var oldBounds = (this.hashedBounds[pool.id][lanes[i].id] || absBounds);
			//oldBounds.moveBy((absBounds.upperLeft().x-lanes[i].bounds.upperLeft().x), (absBounds.upperLeft().y-lanes[i].bounds.upperLeft().y));
			var offset = this.getOffset(lanes[i], true, pool);
			var xOffsetDepth = 0;

			var depth = this.getDepth(lanes[i], pool);
			if (this.hashedLaneDepth[lanes[i].id] !== undefined && this.hashedLaneDepth[lanes[i].id] !== depth) {
				xOffsetDepth = (this.hashedLaneDepth[lanes[i].id] - depth) * 30;
				offset.x += xOffsetDepth;
			}

			j = -1;

			while (++j < children.length) {

				if (xOffsetDepth && !children[j].getStencil().id().endsWith("Lane")) {
					movedShapes.push({ xOffset: xOffsetDepth, shape: children[j] });
					children[j].bounds.moveBy(xOffsetDepth, 0);
				}

				if (children[j].getStencil().id().endsWith("Subprocess")) {
					this.moveChildDockers(children[j], offset);
				}

				var edges = [].concat(children[j].getIncomingShapes())
					.concat(children[j].getOutgoingShapes())
					// Remove all edges which are included in the selection from the list
					.findAll(function (r) { return r instanceof ORYX.Core.Edge })

				k = -1;
				while (++k < edges.length) {

					if (edges[k].getStencil().id().endsWith("MessageFlow")) {
						this.layoutEdges(children[j], [edges[k]], offset);
						continue;
					}

					l = -1;
					while (++l < edges[k].dockers.length) {

						docker = edges[k].dockers[l];

						if (docker.getDockedShape() || docker.isChanged) {
							continue;
						}


						pos = docker.bounds.center();

						// Check if the modified center included the new position
						var isOverLane = oldBounds.isIncluded(pos);
						// Check if the original center is over the pool
						var isOutSidePool = !oldPool.isIncluded(pos);
						var previousIsOverLane = l == 0 ? isOverLane : oldBounds.isIncluded(edges[k].dockers[l - 1].bounds.center());
						var nextIsOverLane = l == edges[k].dockers.length - 1 ? isOverLane : oldBounds.isIncluded(edges[k].dockers[l + 1].bounds.center());
						var off = Object.clone(offset);

						// If the 
						if (isScaled && isOverLane && this.isResized(lanes[i], this.hashedBounds[pool.id][lanes[i].id])) {
							var relY = (pos.y - absBounds.upperLeft().y + off.y);
							off.y -= (relY - (relY * (absBounds.height() / oldBounds.height())));
						}

						// Check if the previous dockers docked shape is from this lane
						// Otherwise, check if the docker is over the lane OR is outside the lane 
						// but the previous/next was over this lane
						if (isOverLane) {
							dockers[docker.id] = { docker: docker, offset: off };
						}
						/*else if (l == 1 && edges[k].dockers.length>2 && edges[k].dockers[l-1].isDocked()){
							var dockedLane = this.getNextLane(edges[k].dockers[l-1].getDockedShape());
							if (dockedLane != lanes[i])
								continue;
							dockers[docker.id] = {docker: docker, offset:offset};
						}
						// Check if the next dockers docked shape is from this lane
						else if (l == edges[k].dockers.length-2 && edges[k].dockers.length>2 && edges[k].dockers[l+1].isDocked()){
							var dockedLane = this.getNextLane(edges[k].dockers[l+1].getDockedShape());
							if (dockedLane != lanes[i])
								continue;
							dockers[docker.id] = {docker: docker, offset:offset};
						}
												
						else if (isOutSidePool) {
							dockers[docker.id] = {docker: docker, offset:this.getOffset(lanes[i], true, pool)};
						}*/


					}
				}

			}
		}

		// Move the moved children 
		var MoveChildCommand = ORYX.Core.Command.extend({
			construct: function (state) {
				this.state = state;
			},
			execute: function () {
				if (this.executed) {
					this.state.each(function (s) {
						s.shape.bounds.moveBy(s.xOffset, 0);
					});
				}
				this.executed = true;
			},
			rollback: function () {
				this.state.each(function (s) {
					s.shape.bounds.moveBy(-s.xOffset, 0);
				});
			}
		})


		// Set dockers
		this.facade.executeCommands([new ORYX.Core.MoveDockersCommand(dockers), new MoveChildCommand(movedShapes)]);

	},

	moveBy: function (pos, offset) {
		pos.x += offset.x;
		pos.y += offset.y;
		return pos;
	},

	getHashedBounds: function (shape) {
		return this.currentPool && this.hashedBounds[this.currentPool.id][shape.id] ? this.hashedBounds[this.currentPool.id][shape.id] : shape.absoluteBounds();
	},

	/**
	 * Returns a set on all child lanes for the given Shape. If recursive is TRUE, also indirect children will be returned (default is FALSE)
	 * The set is sorted with first child the lowest y-coordinate and the last one the highest.
	 * @param {ORYX.Core.Shape} shape
	 * @param {boolean} recursive
	 */
	getLanes: function (shape, recursive) {
		var namespace = this.getNamespace();

		// Get all the child lanes
		var lanes = shape.getChildNodes(recursive || false).findAll(function (node) { return (node.getStencil().id() === namespace + "Lane"); });

		// Sort all lanes by there y coordinate
		lanes = lanes.sort(function (a, b) {

			// Get y coordinates for upper left and lower right
			var auy = Math.round(a.bounds.upperLeft().y);
			var buy = Math.round(b.bounds.upperLeft().y);
			var aly = Math.round(a.bounds.lowerRight().y);
			var bly = Math.round(b.bounds.lowerRight().y);

			var ha = this.getHashedBounds(a);
			var hb = this.getHashedBounds(b);

			// Get the old y coordinates
			var oauy = Math.round(ha.upperLeft().y);
			var obuy = Math.round(hb.upperLeft().y);
			var oaly = Math.round(ha.lowerRight().y);
			var obly = Math.round(hb.lowerRight().y);

			// If equal, than use the old one
			if (auy == buy && aly == bly) {
				auy = oauy; buy = obuy; aly = oaly; bly = obly;
			}

			if (Math.round(a.bounds.height() - ha.height()) === 0 && Math.round(b.bounds.height() - hb.height()) === 0) {
				return auy < buy ? -1 : (auy > buy ? 1 : 0);
			}

			// Check if upper left and lower right is completely above/below
			var above = auy < buy && aly < bly;
			var below = auy > buy && aly > bly;
			// Check if a is above b including the old values
			var slightlyAboveBottom = auy < buy && aly >= bly && oaly < obly;
			var slightlyAboveTop = auy >= buy && aly < bly && oauy < obuy;
			// Check if a is below b including the old values
			var slightlyBelowBottom = auy > buy && aly <= bly && oaly > obly;
			var slightlyBelowTop = auy <= buy && aly > bly && oauy > obuy;

			// Return -1 if a is above b, 1 if b is above a, or 0 otherwise
			return (above || slightlyAboveBottom || slightlyAboveTop ? -1 : (below || slightlyBelowBottom || slightlyBelowTop ? 1 : 0))
		}.bind(this));

		// Return lanes
		return lanes;
	},

	getNamespace: function () {
		if (!this.namespace) {
			var stencilsets = this.facade.getStencilSets();
			if (stencilsets.keys()) {
				this.namespace = stencilsets.keys()[0];
			} else {
				return undefined;
			}
		}
		return this.namespace;
	}
};

var ResizeLanesCommand = ORYX.Core.Command.extend({

	construct: function (shape, parent, pool, plugin) {

		this.facade = plugin.facade;
		this.plugin = plugin;
		this.shape = shape;
		this.changes;

		this.pool = pool;

		this.parent = parent;


		this.shapeChildren = [];

		/*
		 * The Bounds have to be stored 
		 * separate because they would
		 * otherwise also be influenced 
		 */
		this.shape.getChildShapes().each(function (childShape) {
			this.shapeChildren.push({
				shape: childShape,
				bounds: {
					a: {
						x: childShape.bounds.a.x,
						y: childShape.bounds.a.y
					},
					b: {
						x: childShape.bounds.b.x,
						y: childShape.bounds.b.y
					}
				}
			});
		}.bind(this));

		this.shapeUpperLeft = this.shape.bounds.upperLeft();

		// If there is no parent, 
		// correct the abs position with the parents abs.
		/*if (!this.shape.parent) { 
			var pAbs = parent.absoluteXY();
			this.shapeUpperLeft.x += pAbs.x;
			this.shapeUpperLeft.y += pAbs.y;
		}*/
		this.parentHeight = this.parent.bounds.height();

	},

	getLeafLanes: function (lane) {
		var childLanes = this.plugin.getLanes(lane).map(function (child) {
			return this.getLeafLanes(child);
		}.bind(this)).flatten();
		return childLanes.length > 0 ? childLanes : [lane];
	},

	findNewLane: function () {

		var lanes = this.plugin.getLanes(this.parent);

		var leafLanes = this.getLeafLanes(this.parent);
		/*leafLanes = leafLanes.sort(function(a,b){
			var aupl = a.absoluteXY().y;
			var bupl = b.absoluteXY().y;
			return aupl < bupl ? -1 : (aupl > bupl ? 1 : 0)
		})*/
		this.lane = leafLanes.find(function (l) { return l.bounds.upperLeft().y >= this.shapeUpperLeft.y }.bind(this)) || leafLanes.last();
		this.laneUpperLeft = this.lane.bounds.upperLeft();
	},

	execute: function () {

		if (this.changes) {
			this.executeAgain();
			return;
		}

		/* 
		 * Rescue all ChildShapes of the deleted
		 * Shape into the lane that takes its 
		 * place 
		 */

		if (!this.lane) {
			this.findNewLane();
		}

		if (this.lane) {

			var laUpL = this.laneUpperLeft;
			var shUpL = this.shapeUpperLeft;

			var depthChange = this.plugin.getDepth(this.lane, this.parent) - 1;

			this.changes = $H({});

			// Selected lane is BELOW the removed lane
			if (laUpL.y >= shUpL.y) {
				this.lane.getChildShapes().each(function (childShape) {

					/*
					 * Cache the changes for rollback
					 */
					if (!this.changes[childShape.getId()]) {
						this.changes[childShape.getId()] = this.computeChanges(childShape, this.lane, this.lane, this.shape.bounds.height());
					}

					childShape.bounds.moveBy(0, this.shape.bounds.height());
				}.bind(this));

				this.plugin.hashChildShapes(this.lane);

				this.shapeChildren.each(function (shapeChild) {
					shapeChild.shape.bounds.set(shapeChild.bounds);
					shapeChild.shape.bounds.moveBy((shUpL.x - 30) - (depthChange * 30), 0);

					/*
					 * Cache the changes for rollback
					 */
					if (!this.changes[shapeChild.shape.getId()]) {
						this.changes[shapeChild.shape.getId()] = this.computeChanges(shapeChild.shape, this.shape, this.lane, 0);
					}

					this.lane.add(shapeChild.shape);

				}.bind(this));

				this.lane.bounds.moveBy(0, shUpL.y - laUpL.y);

				// Selected lane is ABOVE the removed lane	
			} else if (shUpL.y > laUpL.y) {

				this.shapeChildren.each(function (shapeChild) {
					shapeChild.shape.bounds.set(shapeChild.bounds);
					shapeChild.shape.bounds.moveBy((shUpL.x - 30) - (depthChange * 30), this.lane.bounds.height());

					/*
					 * Cache the changes for rollback
					 */
					if (!this.changes[shapeChild.shape.getId()]) {
						this.changes[shapeChild.shape.getId()] = this.computeChanges(shapeChild.shape, this.shape, this.lane, 0);
					}

					this.lane.add(shapeChild.shape);

				}.bind(this));
			}




		}

		/*
		 * Adjust the height of the lanes
		 */
		// Get the height values
		var oldHeight = this.lane.bounds.height();
		var newHeight = this.lane.length === 1 ? this.parentHeight : this.lane.bounds.height() + this.shape.bounds.height();

		// Set height
		this.setHeight(newHeight, oldHeight, this.parent, this.parentHeight, true);

		// Cache all sibling lanes
		//this.changes[this.shape.getId()] = this.computeChanges(this.shape, this.parent, this.parent, 0);
		this.plugin.getLanes(this.parent).each(function (childLane) {
			if (!this.changes[childLane.getId()] && childLane !== this.lane && childLane !== this.shape) {
				this.changes[childLane.getId()] = this.computeChanges(childLane, this.parent, this.parent, 0);
			}
		}.bind(this))

		// Update
		this.update();
	},

	setHeight: function (newHeight, oldHeight, parent, parentHeight, store) {

		// Set heigh of the lane
		this.plugin.setDimensions(this.lane, this.lane.bounds.width(), newHeight);
		this.plugin.hashedBounds[this.pool.id][this.lane.id] = this.lane.absoluteBounds();

		// Adjust child lanes
		this.plugin.adjustHeight(this.plugin.getLanes(parent), this.lane);

		if (store === true) {
			// Store changes
			this.changes[this.shape.getId()] = this.computeChanges(this.shape, parent, parent, 0, oldHeight, newHeight);
		}

		// Set parents height
		this.plugin.setDimensions(parent, parent.bounds.width(), parentHeight);

		if (parent !== this.pool) {
			this.plugin.setDimensions(this.pool, this.pool.bounds.width(), this.pool.bounds.height() + (newHeight - oldHeight));
		}
	},

	update: function () {

		// Hack to prevent the updating of the dockers
		this.plugin.hashedBounds[this.pool.id]["REMOVED"] = true;
		// Update
		//this.facade.getCanvas().update();
	},

	rollback: function () {

		var laUpL = this.laneUpperLeft;
		var shUpL = this.shapeUpperLeft;

		this.changes.each(function (pair) {

			var parent = pair.value.oldParent;
			var shape = pair.value.shape;
			var parentHeight = pair.value.parentHeight;
			var oldHeight = pair.value.oldHeight;
			var newHeight = pair.value.newHeight;

			// Move siblings
			if (shape.getStencil().id().endsWith("Lane")) {
				shape.bounds.moveTo(pair.value.oldPosition);
			}

			// If lane
			if (oldHeight) {
				this.setHeight(oldHeight, newHeight, parent, parent.bounds.height() + (oldHeight - newHeight));
				if (laUpL.y >= shUpL.y) {
					this.lane.bounds.moveBy(0, this.shape.bounds.height() - 1);
				}
			} else {
				parent.add(shape);
				shape.bounds.moveTo(pair.value.oldPosition);

			}


		}.bind(this));

		// Update
		//this.update();

	},

	executeAgain: function () {

		this.changes.each(function (pair) {
			var parent = pair.value.newParent;
			var shape = pair.value.shape;
			var newHeight = pair.value.newHeight;
			var oldHeight = pair.value.oldHeight;

			// If lane
			if (newHeight) {
				var laUpL = this.laneUpperLeft.y;
				var shUpL = this.shapeUpperLeft.y;

				if (laUpL >= shUpL) {
					this.lane.bounds.moveBy(0, shUpL - laUpL);
				}
				this.setHeight(newHeight, oldHeight, parent, parent.bounds.height() + (newHeight - oldHeight));
			} else {
				parent.add(shape);
				shape.bounds.moveTo(pair.value.newPosition);
			}

		}.bind(this));

		// Update
		this.update();
	},

	computeChanges: function (shape, oldParent, parent, yOffset, oldHeight, newHeight) {

		oldParent = this.changes[shape.getId()] ? this.changes[shape.getId()].oldParent : oldParent;
		var oldPosition = this.changes[shape.getId()] ? this.changes[shape.getId()].oldPosition : shape.bounds.upperLeft();

		var sUl = shape.bounds.upperLeft();

		var pos = { x: sUl.x, y: sUl.y + yOffset };

		var changes = {
			shape: shape,
			parentHeight: oldParent.bounds.height(),
			oldParent: oldParent,
			oldPosition: oldPosition,
			oldHeight: oldHeight,
			newParent: parent,
			newPosition: pos,
			newHeight: newHeight
		};

		return changes;
	}

});


ORYX.Plugins.BPMN2_0 = ORYX.Plugins.AbstractPlugin.extend(ORYX.Plugins.BPMN2_0);
